comp.lang.ada
 help / color / mirror / Atom feed
* Avoiding dispatching in procedure's with classwide types
@ 2016-05-28 19:01 Jeremiah
  2016-06-06  3:12 ` rieachus
  2016-06-06  3:24 ` rieachus
  0 siblings, 2 replies; 8+ messages in thread
From: Jeremiah @ 2016-05-28 19:01 UTC (permalink / raw)


I have a procedure that uses one tagged type (the primitive type) and a classwide type.  Normally with a classwide type parameter, you expect the internals of the procedure to dispatch on the type (I'm assuming here...could be wrong).  However, I can theoretically avoid dispatching and manually do the things I want to since both types reside in the same package.  

The upside to this I *think* is efficiency (if I use this procedure on each element of a large array I could reduce the time it takes to do all the work possibly).

The downside I can imagine is that I could possibly break how a child class of the classwide type worked because I was only making changes with the base class in mind.

That said, the procedure doesn't specify how the internals work, so the caller wouldn't even know what procedures I would use or if I would use any.  Additionally if I didn't have a procedure to do something to the data members, then I would have to mess with them directly anyways.

Kind of a small example:
procedure Class_Wide_Example
  (Target : in out Tagged_Type;  
   Source : in Base_Type'Class) 
is
begin
   --  Here I could use a function or procedure call to Get_Accessor()
   --  Something like Target.Some_Accessor := Get_Accessor(Source);
   --  In theory, descendants of Source might have some extra protection around
   --  the access value being returned and doing it as below might bypass that
   --  protection.
   Target.Some_Accessor := Source.Same_Type_Of_Accessor;
end Class_Wide_Example;

I feel like this is unsafe.  However, my actual examples can get a bit more complex, so I could see a lot of dispatching calls adding up if you have large arrays of these types all calling this procedure.  I may just be overthinking this.  

However, I wanted to see what the general thought process is.  Is it ok to avoid dispatching in a procedure with a classwide type?  If not, how do you handle scenarios where you need to manage internal private data members?


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: Avoiding dispatching in procedure's with classwide types
  2016-05-28 19:01 Avoiding dispatching in procedure's with classwide types Jeremiah
@ 2016-06-06  3:12 ` rieachus
  2016-06-07  2:23   ` Jeremiah
  2016-06-06  3:24 ` rieachus
  1 sibling, 1 reply; 8+ messages in thread
From: rieachus @ 2016-06-06  3:12 UTC (permalink / raw)


On Saturday, May 28, 2016 at 3:01:37 PM UTC-4, Jeremiah wrote:
> I have a procedure that uses one tagged type (the primitive type) and a classwide type.

1) I have no clue as to what you are trying to do here.
2) I have no idea why you think that this would improve performance.

If you have a subroutine with a parameter of a class wide type (Foo'Class), dispatching happens.  If you have a parameter of the base type (Foo), there is no dispatching, however there may be inheritance. If a subprogram of a type is declared in the same package spec as the type, you get inheritance.  So:

  package P is
    type Foo is private;
    procedure Put(F: in out Foo);
    function Get return Foo;
  ...
  end P;
 
  with P;
  package Q is 
    type Bar is new P.Foo with ...;
    -- Bar has a procedure Put and a function Get.
  end Q;


   Temp_Foo: P.Foo := P.Get;
   Barf: Q.Bar := Temp_Foo;
 begin 
   Put(Foo(Barf));

The only way you could do something like what you are proposing is to instead of using an operation of a class wide type, do a type conversion to the parent type, and call its operation:

  Fooc: Foo'Class := Get;
begin
  Put(Fooc);  -- normal
  Put(Foo(Fooc));

If the descendants of Foo add no new fields, the two calls will be equivalent.  If Bar or other descendants add state, that state will be stripped off in the bottom call to Put, and you will get the Put of Foo not of Bar.  Why you would want to bake such trouble in a program is beyond me.

Notice the extra effort to be able to call Get and assign the result to a variable of type Foo.  Also notice that you may have to use declare blocks to create objects of indefinite sizes.


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: Avoiding dispatching in procedure's with classwide types
  2016-05-28 19:01 Avoiding dispatching in procedure's with classwide types Jeremiah
  2016-06-06  3:12 ` rieachus
@ 2016-06-06  3:24 ` rieachus
  1 sibling, 0 replies; 8+ messages in thread
From: rieachus @ 2016-06-06  3:24 UTC (permalink / raw)


If you want to speed things up, add a procedure that may do just that--and time it:

  type Foo;

  procedure Operate(F: in out Foo);

  type Foobar is array (Integer range <>) of Foo;

Now compare:

 for I in FB'range loop
   Operate(FB(I));
 end loop;

to:

 procedure Array_Operate(FB: in out Foobar) is
 begin
   for I in FB'range loop
     Operate(FB(I));
   end loop;
 end Array_Operate;
...
 Array_Operate;

This eliminates the procedure call overhead from each time around the loop.  Also note that you can extend this to the dispatching case and have only one dispatch.  (Assuming the array does not contain mixed descendants of Foo.)  If there is a mixture?  You almost certainly want the individual dispatches.


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: Avoiding dispatching in procedure's with classwide types
  2016-06-06  3:12 ` rieachus
@ 2016-06-07  2:23   ` Jeremiah
  2016-06-07  7:43     ` Dmitry A. Kazakov
  0 siblings, 1 reply; 8+ messages in thread
From: Jeremiah @ 2016-06-07  2:23 UTC (permalink / raw)


On Sunday, June 5, 2016 at 11:12:13 PM UTC-4, riea...@comcast.net wrote:
> On Saturday, May 28, 2016 at 3:01:37 PM UTC-4, Jeremiah wrote:
> > I have a procedure that uses one tagged type (the primitive type) and a classwide type.
> 
> 1) I have no clue as to what you are trying to do here.
> 2) I have no idea why you think that this would improve performance.

Here's a small example I cooked up.  It's a useless example, but hopefully illustrates the problem better.

Say a package declares two types as so:
**************************************************
package Test_Pkg_A is

   type Base_Class_A is tagged limited private;
   procedure Set
      (Object : in out Base_Class_A;
       Value  : in     Integer);
   function Get
      (Object : in Base_Class_A)
       return Integer;
   
   type Base_Class_B is tagged limited private;
   procedure Set
      (Object : in out Base_Class_B;
       Value  : in     Integer);
   function Get
      (Object : in Base_Class_B)
       return Integer;
   
   procedure Copy_Version_1
      (Target : in out Base_Class_A;
       Source : in     Base_Class_B'Class);
   procedure Copy_Version_2
      (Target : in out Base_Class_A;
       Source : in     Base_Class_B'Class);
   
private
   
   type Base_Class_A is tagged limited record
      Value : Integer := 0;
   end record;
   
   type Base_Class_B is tagged limited record
      Value : Integer := 0;
   end record;

end Test_Pkg_A;
**************************************************

The body for these packages is:
**************************************************
package body Test_Pkg_A is
   
   procedure Set
      (Object : in out Base_Class_A;
       Value  : in     Integer)
   is
   begin
      Object.Value := Value;
   end Set;
   
   function Get
      (Object : in Base_Class_A)
       return Integer
   is
   begin
      return Object.Value;
   end Get;
   
   procedure Set
      (Object : in out Base_Class_B;
       Value  : in     Integer)
   is
   begin
      Object.Value := Value;
   end Set;
   
   function Get
      (Object : in Base_Class_B)
       return Integer
   is
   begin
      return Object.Value;
   end Get;
   
   procedure Copy_Version_1
      (Target : in out Base_Class_A;
       Source : in     Base_Class_B'Class)
   is
   begin
      --  This seems a bit dangerous since it bypasses dispatching on Source???
      Target.Value := Source.Value;
   end Copy_Version_1;
   
   procedure Copy_Version_2
      (Target : in out Base_Class_A;
       Source : in     Base_Class_B'Class)
   is
   begin
      --  This seems safer.
      Target.Value := Get(Source);
   end Copy_Version_2;
   
   
   
end Test_Pkg_A;
**************************************************

Notice how both Copy_Version_1 and Copy_Version_2 both take in a class wide source.  However, Copy_Version_1 directly copies the parameter while Copy_Version_2 uses a call to a dispatching Get() function.

If the Source passed in overrides Get, then both of the Copy Functions could give very different answers.  Say for example:

**************************************************
with Test_Pkg_A;

package Test_Pkg_B is

   type Derived_Class_A is new Test_Pkg_A.Base_Class_B with null record;
   
   overriding 
   function Get
      (Object : in Derived_Class_A)
       return Integer;

end Test_Pkg_B;
**************************************************

with body:
**************************************************
package body Test_Pkg_B is
   overriding 
   function Get
      (Object : in Derived_Class_A)
       return Integer
   is
      Value : Integer := Test_Pkg_A.Base_Class_B(Object).Get;
   begin
      if Value > 2 then
         return Value;
      else
         return 0;
      end if;
   end Get;
end Test_Pkg_B;
**************************************************

Here for whatever reason, the Get function for Derived_Class_A bounds the value from returning as a 1 (this is just a random example here).

Now if you have the following main:
**************************************************
with Test_Pkg_A;
with Test_Pkg_B;

with Ada.Text_IO;

procedure Ada_Main is
   Target_A : Test_Pkg_A.Base_Class_A;
   Target_B : Test_Pkg_A.Base_Class_A;
   
   Source_A : Test_Pkg_B.Derived_Class_A;
   Source_B : Test_Pkg_B.Derived_Class_A;
begin
   
   Source_A.Set(1);
   Source_B.Set(1);
   
   Test_Pkg_A.Copy_Version_1
      (Target => Target_A,
       Source => Source_A);
   Test_Pkg_A.Copy_Version_2
      (Target => Target_B,
       Source => Source_B);
   
   Ada.Text_IO.Put_Line
      ("Target_A: " 
       & Integer'Image(Target_A.Get));
   Ada.Text_IO.Put_Line
      ("Target_B: " 
       & Integer'Image(Target_B.Get));
   
   
end Ada_Main;
**************************************************

you get the output:
**************************************************
D:\__workspaces\Ada\Test_Ada\obj\ada_main
Target_A:  1
Target_B:  0
[2016-06-06 22:03:01] process terminated successfully, elapsed time: 02.38s
**************************************************

So my question is if the method used in Copy_Value_1 considered a no-no or since it bypasses dispatching in favor of directly copying the values in a procedure that takes a class wide type?  Or is it ok since there is no guarantee to what the internals of the Copy_Value_1 procedure are?  My gut is it is bad form, but I wanted to know if I was just over thinking it.

As for why I would think it would improve performance:  GNAT doesn't tend to optimize out dispatching calls (at least not in any code I have seen yet) and I do have a couple of more complex types that would have to call 3 or 4 dispatching calls in a procedure with a class wide type passed in.  These calls would be operated on large arrays of objects based on those types.  Taking out the dispatching could reduce the time iterating through those arrays.  But it might also leave the procedure open to bugs on derived children?

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: Avoiding dispatching in procedure's with classwide types
  2016-06-07  2:23   ` Jeremiah
@ 2016-06-07  7:43     ` Dmitry A. Kazakov
  2016-06-07 11:30       ` Jeremiah
  0 siblings, 1 reply; 8+ messages in thread
From: Dmitry A. Kazakov @ 2016-06-07  7:43 UTC (permalink / raw)


On 07/06/2016 04:23, Jeremiah wrote:

> So my question is if the method used in Copy_Value_1 considered a
> no-no or since it bypasses dispatching in favor of directly copying the
> values in a procedure that takes a class wide type? Or is it ok since
> there is no guarantee to what the internals of the Copy_Value_1
> procedure are? My gut is it is bad form, but I wanted to know if I was
> just over thinking it.

The answer depends in your example on whether it represents a full 
multiple dispatch or not. Assignment is multiple dispatch by definition. 
So the implementation you presented is in general wrong. But, *IF* one 
of the types involved is final and does not have any descendants the 
implementation is OK.

In the declarations provided, both types are tagged and thus there is no 
hint that one of them were final. Therefore in order to make it right 
one of the types must be made, at least, publicly untagged.

There is an Ada design flaw that privately tagged types easily leak into 
public declarations, so, possibly, the tagged type should be wrapped 
into a non-tagged record.

> As for why I would think it would improve performance:

No. Multiple dispatch should be far faster that its cascaded poor-man's 
emulation through single dispatches, in case it is really a multiple 
dispatch.

> GNAT doesn't
> tend to optimize out dispatching calls

There is no need to "optimize" them. The design of dispatch in Ada is 
such that when you could optimize it, there should be a specific type 
already and the call non-dispatching. All "optimization" is already 
here, static on the language level. Anything else, e.g. re-dispatch 
cases are software design bugs, IMO.

> But it might also leave the procedure
> open to bugs on derived children?

If you violate typing then yes. Assignment is an MD operation. Its 
implementation as a non-MD operation violates typing and thus is open to 
any bugs.

-- 
Regards,
Dmitry A. Kazakov
http://www.dmitry-kazakov.de


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: Avoiding dispatching in procedure's with classwide types
  2016-06-07  7:43     ` Dmitry A. Kazakov
@ 2016-06-07 11:30       ` Jeremiah
  2016-06-07 21:05         ` Randy Brukardt
  0 siblings, 1 reply; 8+ messages in thread
From: Jeremiah @ 2016-06-07 11:30 UTC (permalink / raw)


On Tuesday, June 7, 2016 at 3:44:33 AM UTC-4, Dmitry A. Kazakov wrote:
> On 07/06/2016 04:23, Jeremiah wrote:
> 
> > So my question is if the method used in Copy_Value_1 considered a
> > no-no or since it bypasses dispatching in favor of directly copying the
> > values in a procedure that takes a class wide type? Or is it ok since
> > there is no guarantee to what the internals of the Copy_Value_1
> > procedure are? My gut is it is bad form, but I wanted to know if I was
> > just over thinking it.
> 
> The answer depends in your example on whether it represents a full 
> multiple dispatch or not. Assignment is multiple dispatch by definition. 
> So the implementation you presented is in general wrong. But, *IF* one 
> of the types involved is final and does not have any descendants the 
> implementation is OK.
> 
> In the declarations provided, both types are tagged and thus there is no 
> hint that one of them were final. Therefore in order to make it right 
> one of the types must be made, at least, publicly untagged.
> 
> There is an Ada design flaw that privately tagged types easily leak into 
> public declarations, so, possibly, the tagged type should be wrapped 
> into a non-tagged record.
> 
> > As for why I would think it would improve performance:
> 
> No. Multiple dispatch should be far faster that its cascaded poor-man's 
> emulation through single dispatches, in case it is really a multiple 
> dispatch.
> 
> > GNAT doesn't
> > tend to optimize out dispatching calls
> 
> There is no need to "optimize" them. The design of dispatch in Ada is 
> such that when you could optimize it, there should be a specific type 
> already and the call non-dispatching. All "optimization" is already 
> here, static on the language level. Anything else, e.g. re-dispatch 
> cases are software design bugs, IMO.
> 
> > But it might also leave the procedure
> > open to bugs on derived children?
> 
> If you violate typing then yes. Assignment is an MD operation. Its 
> implementation as a non-MD operation violates typing and thus is open to 
> any bugs.
> 
> -- 
> Regards,
> Dmitry A. Kazakov
> http://www.dmitry-kazakov.de

Thanks!  That was my feeling.  I'll probably go back and make the types final then.  Kinda sucks because I like the dot notation for function calling.  I wonder why they chose to make the dot notation require a tagged type?

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: Avoiding dispatching in procedure's with classwide types
  2016-06-07 11:30       ` Jeremiah
@ 2016-06-07 21:05         ` Randy Brukardt
  2016-06-09  1:12           ` Jeremiah
  0 siblings, 1 reply; 8+ messages in thread
From: Randy Brukardt @ 2016-06-07 21:05 UTC (permalink / raw)


"Jeremiah" <jeremiah.breeden@gmail.com> wrote in message 
news:ed73869e-5316-4cf3-bc76-e192f14ef506@googlegroups.com...
> I wonder why they chose to make the dot notation require a tagged type?

Mainly because there were semantic problems with allowing prefixed notation 
on some kinds of untagged types (esp. access types). Because Ada tries hard 
not to break privacy for Legality Rules, disallowing access types also 
requires disallowing any untagged private types (as the full type might have 
been access). Since essentially all ADTs should be tagged (and controlled) 
these days, just saying tagged seemed like the best way to deal with the 
issues.

I have to wonder if a "final" for a tagged type (not an operation) would be 
useful, as there's no real good reason to hide taggedness (and lots of good 
reasons not to hide it, prefix notation being exhibit A).

                                     Randy.


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: Avoiding dispatching in procedure's with classwide types
  2016-06-07 21:05         ` Randy Brukardt
@ 2016-06-09  1:12           ` Jeremiah
  0 siblings, 0 replies; 8+ messages in thread
From: Jeremiah @ 2016-06-09  1:12 UTC (permalink / raw)


On Tuesday, June 7, 2016 at 5:05:37 PM UTC-4, Randy Brukardt wrote:
> Mainly because there were semantic problems with allowing prefixed notation 
> on some kinds of untagged types (esp. access types). Because Ada tries hard 
> not to break privacy for Legality Rules, disallowing access types also 
> requires disallowing any untagged private types (as the full type might have 
> been access). Since essentially all ADTs should be tagged (and controlled) 
> these days, just saying tagged seemed like the best way to deal with the 
> issues.
Ok, that makes sense.


> I have to wonder if a "final" for a tagged type (not an operation) would be 
> useful, as there's no real good reason to hide taggedness (and lots of good 
> reasons not to hide it, prefix notation being exhibit A).
> 
>                                      Randy.
I could get behind that as that would solve my issue.  Plus it shouldn't break existing code. Should still be able to subtype it?

^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2016-06-09  1:12 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-05-28 19:01 Avoiding dispatching in procedure's with classwide types Jeremiah
2016-06-06  3:12 ` rieachus
2016-06-07  2:23   ` Jeremiah
2016-06-07  7:43     ` Dmitry A. Kazakov
2016-06-07 11:30       ` Jeremiah
2016-06-07 21:05         ` Randy Brukardt
2016-06-09  1:12           ` Jeremiah
2016-06-06  3:24 ` rieachus

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox