comp.lang.ada
 help / color / mirror / Atom feed
* Inherited Methods and such
@ 2007-09-17 14:26 shaunpatterson
  2007-09-17 15:11 ` Ludovic Brenta
  2007-09-18  4:03 ` Steve
  0 siblings, 2 replies; 74+ messages in thread
From: shaunpatterson @ 2007-09-17 14:26 UTC (permalink / raw)


Hi,

    I'm trying to get sub-packages to override superclass methods.
Specifically, I'm trying to implement a Template Design pattern
in Ada.

I want a function in the base package that calls two methods
and some other code.  Something like:


--- BASE CLASS ---

package body Base is

procedure Load is begin
   go;
end Load;

procedure Test1 is begin
   Put_Line ("Test1 FROM BASE CLASS");
end Test1;

procedure Test2 is begin
    Put_Line ("Test2 FROM BASE CLASS");
end Test2;

procedure Do is begin
   Test1;
   Put_Line ("Doing something");
   Test2;

end Do;

end Base;

---------

then in the sub classes, I'd like to over ride Test1 and 2 into
something like:

package Base.SubclassA is
   procedure Test1 is begin
       Put_Line ("Test1 FROM SUBCLASS A");
   end Test1;

   procedure Test2 is begin
        Put_Line ("Test2 FROM SUBCLASS A");
   end Test2;
end Base.SubclassA;


---------------------

Now I'd like to call it something like:

if UsingBaseClass then

     Base.go;

else

     Base.SubclassA.go;

end if;


--------------------

If the baseclass test passes, I'd like the output:

Test1 FROM BASE CLASS
doing something
Test2 FROM BASE CLASS

else

Test1 FROM SUBCLASSA
doing something
Test2 FROM SUBCLASSA



Is this type of method inheritance supported in ada? If so,
where am I going wrong?

Thanks
--
Shaun Patterson




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

* Re: Inherited Methods and such
  2007-09-17 14:26 Inherited Methods and such shaunpatterson
@ 2007-09-17 15:11 ` Ludovic Brenta
  2007-09-17 16:46   ` shaunpatterson
  2007-09-17 20:22   ` Maciej Sobczak
  2007-09-18  4:03 ` Steve
  1 sibling, 2 replies; 74+ messages in thread
From: Ludovic Brenta @ 2007-09-17 15:11 UTC (permalink / raw)


Shaun Patterson wrote:
> I'm trying to get sub-packages to override superclass methods.

Packages are not classes. IIUC, what you want is dynamic dispatching:
Base.Do should call whichever version of Test_1 and Test_2 is
applicable for the current invocation.

In Ada, you get dynamic dispatching by combining tagged types and
primitive operations on these types.

See http://en.wikibooks.org/wiki/Ada_Programming/Object_Orientation

If you come from C++:

class -> tagged type
* -> class
namespace -> package (very roughly)
virtual method -> primitive operation
static method -> class-wide operation

In Ada, a "class" is the set consisting of a tagged type and all types
derived from it.

HTH

--
Ludovic Brenta.




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

* Re: Inherited Methods and such
  2007-09-17 15:11 ` Ludovic Brenta
@ 2007-09-17 16:46   ` shaunpatterson
  2007-09-17 19:07     ` Ludovic Brenta
  2007-09-17 20:22   ` Maciej Sobczak
  1 sibling, 1 reply; 74+ messages in thread
From: shaunpatterson @ 2007-09-17 16:46 UTC (permalink / raw)


Yes this is what I suspected.  I was hoping I was overlooking
something.




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

* Re: Inherited Methods and such
  2007-09-17 16:46   ` shaunpatterson
@ 2007-09-17 19:07     ` Ludovic Brenta
  0 siblings, 0 replies; 74+ messages in thread
From: Ludovic Brenta @ 2007-09-17 19:07 UTC (permalink / raw)


shaunpatterson@gmail.com writes:
> Yes this is what I suspected.  I was hoping I was overlooking
> something.

You hoped correctly :)  Does your program work now?

-- 
Ludovic Brenta.



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

* Re: Inherited Methods and such
  2007-09-17 15:11 ` Ludovic Brenta
  2007-09-17 16:46   ` shaunpatterson
@ 2007-09-17 20:22   ` Maciej Sobczak
  2007-09-17 21:07     ` Ludovic Brenta
  1 sibling, 1 reply; 74+ messages in thread
From: Maciej Sobczak @ 2007-09-17 20:22 UTC (permalink / raw)


On 17 Wrz, 17:11, Ludovic Brenta <ludo...@ludovic-brenta.org> wrote:

> If you come from C++:
>
> class -> tagged type

Not really. Tagged type is analogous to a polymorphic class in C++ (a
class that has at least one virtual function).

> * -> class

You mean pointer or reference to the base class in C++ is analogous to
the class-wide type in Ada (roughly - in C++ they are have always
referential, not copy semantics).

> namespace -> package (very roughly)

OK (very roughly). :-)

> virtual method -> primitive operation

OK.

> static method -> class-wide operation

Not really. Class-wide operation in Ada is similar to non-virtual
function in C++.

> In Ada, a "class" is the set consisting of a tagged type and all types
> derived from it.

--
Maciej Sobczak
http://www.msobczak.com/




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

* Re: Inherited Methods and such
  2007-09-17 20:22   ` Maciej Sobczak
@ 2007-09-17 21:07     ` Ludovic Brenta
  2007-09-18 14:27       ` Maciej Sobczak
  2007-09-18 14:27       ` Maciej Sobczak
  0 siblings, 2 replies; 74+ messages in thread
From: Ludovic Brenta @ 2007-09-17 21:07 UTC (permalink / raw)


Maciej Sobczak <see.my.homepage@gmail.com> writes:

> On 17 Wrz, 17:11, Ludovic Brenta <ludo...@ludovic-brenta.org> wrote:
>
>> If you come from C++:
>>
>> class -> tagged type
>
> Not really. Tagged type is analogous to a polymorphic class in C++ (a
> class that has at least one virtual function).

So, you are saying that

class foo { };

is not a class in C++?

>> * -> class
>
> You mean pointer or reference to the base class in C++ is analogous to
> the class-wide type in Ada (roughly - in C++ they are have always
> referential, not copy semantics).

No, I meant C++ does not have a notation for Ada's concept of a class.
(You may turn it the other way around and say C++ does not have a
notation for a pointer to a specific type.)

>> namespace -> package (very roughly)
>
> OK (very roughly). :-)
>
>> virtual method -> primitive operation
>
> OK.
>
>> static method -> class-wide operation
>
> Not really. Class-wide operation in Ada is similar to non-virtual
> function in C++.

I stand corrected.  The last time I wrote any C++ was back in 2001 :)

-- 
Ludovic Brenta.



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

* Re: Inherited Methods and such
  2007-09-17 14:26 Inherited Methods and such shaunpatterson
  2007-09-17 15:11 ` Ludovic Brenta
@ 2007-09-18  4:03 ` Steve
  1 sibling, 0 replies; 74+ messages in thread
From: Steve @ 2007-09-18  4:03 UTC (permalink / raw)


<shaunpatterson@gmail.com> wrote in message 
news:1190039166.449906.15070@g4g2000hsf.googlegroups.com...
> Hi,
>
[snip]
> Is this type of method inheritance supported in ada? If so,
> where am I going wrong?
>

Yes this type of method inheritance is supported in Ada, but you're using 
the wrong constructs.

The Ada equivalent to a C++ class is a tagged type.

In C++ you may declare a class:

class Base is
{
    public:
        virtual void Test1();
};

In Ada the equivalent is::

  type Base is tagged null record;

  procedure Test1( obj : Base );


To derive a subclass from Base in C++ you would use:

class Derived : public Base
{
    public:
        virtual void Test1();
};

The Ada equivalent is:

  type Derived is new Base with null record;

  procedure Test1( obj : Derived );

In C++ to declare an objects of type Base an Derived and to invoke their 
Test1 functions you use:

  Base obj1;
  Derived obj2;

  obj1.Test1();
  obj2.Test1();

The Ada 95 equivalent is:

  declare
    obj1 : Base;
    obj2 : Derived;
  begin
    Test1( obj1 );
    Test1( obj2 );
  end;

Or in Ada 2005 you may use the alternative syntax:


  declare
    obj1 : Base;
    obj2 : Derived;
  begin
    obj1.Test1;
    obj2.Test1;
  end;

The sample code below does some of what your example code was doing, and may 
be helpful in helping to sort things out.

-------------------------------
package Base_Package is

   type Base_Class is tagged null record;

   procedure Test1( base : Base_Class );

   procedure Test2( base : Base_Class );

end Base_Package;

-------------------------------
with Ada.Text_IO;
 use Ada.Text_IO;
package body Base_Package is

   procedure Test1( base : Base_Class ) is
   begin
     Put_Line( "Test1 FROM BASE CLASS");
   end Test1;

   procedure Test2( base : Base_Class ) is
   begin
     Put_Line( "Test2 FROM BASE CLASS");
   end Test2;

end Base_Package;

-------------------------------
with Base_Package;
 use Base_Package;
package Base_Subclass_Package is

   type Base_Subclass is new Base_Class with null record;

   procedure Test1( base : Base_Subclass );

   procedure Test2( base : Base_Subclass );

end Base_Subclass_Package;

-------------------------------
with Ada.Text_IO;
 use Ada.Text_IO;
package body Base_Subclass_Package is

   procedure Test1( base : Base_Subclass ) is
   begin
     Put_Line( "Test1 FROM BASE SUBCLASS");
   end Test1;

   procedure Test2( base : Base_Subclass ) is
   begin
     Put_Line( "Test2 FROM BASE SUBCLASS");
   end Test2;

end Base_Subclass_Package;

-------------------------------
with Base_Package;
 use Base_Package;
with Base_Subclass_Package;
 use Base_Subclass_Package;

procedure Run_Test is
   a : Base_Class;
   b : Base_Subclass;

   procedure DoIt( base : Base_Class'class ) is
   begin
     Test1( base );
   end DoIt;

begin
  DoIt( a );
  DoIt( b );
end Run_Test;

Regards,
Steve
(The Duck)

> Thanks
> --
> Shaun Patterson
> 





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

* Re: Inherited Methods and such
  2007-09-17 21:07     ` Ludovic Brenta
@ 2007-09-18 14:27       ` Maciej Sobczak
  2007-09-18 14:27       ` Maciej Sobczak
  1 sibling, 0 replies; 74+ messages in thread
From: Maciej Sobczak @ 2007-09-18 14:27 UTC (permalink / raw)


On 17 Wrz, 23:07, Ludovic Brenta <ludo...@ludovic-brenta.org> wrote:

> >> class -> tagged type
>
> > Not really. Tagged type is analogous to a polymorphic class in C++ (a
> > class that has at least one virtual function).
>
> So, you are saying that
>
> class foo { };
>
> is not a class in C++?

This is not analogous to *tagged* type in Ada.
For the type above, there is no "dope" and it is not possible to query
the object of "class-wide type" about its specific type.

The closest analogy to the above is just a (empty) non-tagged record.

> >> * -> class
>
> > You mean pointer or reference to the base class in C++ is analogous to
> > the class-wide type in Ada (roughly - in C++ they are have always
> > referential, not copy semantics).
>
> No, I meant C++ does not have a notation for Ada's concept of a class.
> (You may turn it the other way around and say C++ does not have a
> notation for a pointer to a specific type.)

Then I'll pick that latter option. There is no pointer/reference to a
specific type in C++.

--
Maciej Sobczak
http://www.msobczak.com/




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

* Re: Inherited Methods and such
  2007-09-17 21:07     ` Ludovic Brenta
  2007-09-18 14:27       ` Maciej Sobczak
@ 2007-09-18 14:27       ` Maciej Sobczak
  2007-09-18 15:25         ` Dmitry A. Kazakov
  1 sibling, 1 reply; 74+ messages in thread
From: Maciej Sobczak @ 2007-09-18 14:27 UTC (permalink / raw)


On 17 Wrz, 23:07, Ludovic Brenta <ludo...@ludovic-brenta.org> wrote:

> >> class -> tagged type
>
> > Not really. Tagged type is analogous to a polymorphic class in C++ (a
> > class that has at least one virtual function).
>
> So, you are saying that
>
> class foo { };
>
> is not a class in C++?

This is not analogous to *tagged* type in Ada.
For the type above, there is no "dope" and it is not possible to query
the object of "class-wide type" about its specific type.

The closest analogy to the above is just a (empty) non-tagged record.

> >> * -> class
>
> > You mean pointer or reference to the base class in C++ is analogous to
> > the class-wide type in Ada (roughly - in C++ they are have always
> > referential, not copy semantics).
>
> No, I meant C++ does not have a notation for Ada's concept of a class.
> (You may turn it the other way around and say C++ does not have a
> notation for a pointer to a specific type.)

Then I'll pick that latter option. There is no pointer/reference to a
specific type in C++.

--
Maciej Sobczak
http://www.msobczak.com/




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

* Re: Inherited Methods and such
  2007-09-18 14:27       ` Maciej Sobczak
@ 2007-09-18 15:25         ` Dmitry A. Kazakov
  2007-09-18 18:34           ` Ludovic Brenta
  2007-09-18 20:39           ` Maciej Sobczak
  0 siblings, 2 replies; 74+ messages in thread
From: Dmitry A. Kazakov @ 2007-09-18 15:25 UTC (permalink / raw)


On Tue, 18 Sep 2007 07:27:36 -0700, Maciej Sobczak wrote:

> On 17 Wrz, 23:07, Ludovic Brenta <ludo...@ludovic-brenta.org> wrote:
> 
>> No, I meant C++ does not have a notation for Ada's concept of a class.

Neither Ada has (I mean a "notation of concept" (:-)). But T& is close to
T'Class, and IMO the concept of a class as a set of types [obtained upon
inheritance] and a type to represent that set is same in both languages.
Ada separates the set and its representative, C++ does not.

>> (You may turn it the other way around and say C++ does not have a
>> notation for a pointer to a specific type.)
> 
> Then I'll pick that latter option. There is no pointer/reference to a
> specific type in C++.

"this" within a constructor/destructor?

A pointer variable pointing to an object under construction/destruction? I
don't know how the C++ standard defines this, as implementation-dependent,
or else as erroneous or else as "specific" (in Ada terms).

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



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

* Re: Inherited Methods and such
  2007-09-18 15:25         ` Dmitry A. Kazakov
@ 2007-09-18 18:34           ` Ludovic Brenta
  2007-09-18 19:29             ` Dmitry A. Kazakov
  2007-09-18 20:39           ` Maciej Sobczak
  1 sibling, 1 reply; 74+ messages in thread
From: Ludovic Brenta @ 2007-09-18 18:34 UTC (permalink / raw)


Dmitry A. Kazakov writes:
>> On 17 Wrz, 23:07, Ludovic Brenta <ludo...@ludovic-brenta.org> wrote:
>> 
>>> No, I meant C++ does not have a notation for Ada's concept of a class.
>
> Neither Ada has (I mean a "notation of concept" (:-)). But T& is
> close to T'Class, and IMO the concept of a class as a set of types
> [obtained upon inheritance] and a type to represent that set is same
> in both languages.  Ada separates the set and its representative,
> C++ does not.

I'm not following you.  Do you mean to say that T'Class is not a
notation for Ada's concept of a class?

-- 
Ludovic Brenta.




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

* Re: Inherited Methods and such
  2007-09-18 18:34           ` Ludovic Brenta
@ 2007-09-18 19:29             ` Dmitry A. Kazakov
  2007-09-18 19:39               ` Ludovic Brenta
  2007-09-18 21:10               ` Simon Wright
  0 siblings, 2 replies; 74+ messages in thread
From: Dmitry A. Kazakov @ 2007-09-18 19:29 UTC (permalink / raw)


On Tue, 18 Sep 2007 20:34:13 +0200, Ludovic Brenta wrote:

> Dmitry A. Kazakov writes:
>>> On 17 Wrz, 23:07, Ludovic Brenta <ludo...@ludovic-brenta.org> wrote:
>>> 
>>>> No, I meant C++ does not have a notation for Ada's concept of a class.
>>
>> Neither Ada has (I mean a "notation of concept" (:-)). But T& is
>> close to T'Class, and IMO the concept of a class as a set of types
>> [obtained upon inheritance] and a type to represent that set is same
>> in both languages.  Ada separates the set and its representative,
>> C++ does not.
> 
> I'm not following you.  Do you mean to say that T'Class is not a
> notation for Ada's concept of a class?

Yes. IMO, concepts do not have notations, maybe in category theory, but not
in a programming language [*]. In Ada T'Class *denotes* a concrete type. It
does not denote the concepts of type, of class, of computing, of late
binding etc.

In a concrete program T'Class may express a concept in the application
domain space. The latter an OOA theologian would call "class." (Did you
mean this?) They argue that there exists some inherent objective relation
between dogs (canis lupus familiaris), the word "dog," and Dog'Class. That
IMO is beyond the language Ada, and is a purely religious concept. (Sorry
for a pun (:-))

--------------------------------
* Maybe, in Prolog, in some very narrow and specific sense.

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



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

* Re: Inherited Methods and such
  2007-09-18 19:29             ` Dmitry A. Kazakov
@ 2007-09-18 19:39               ` Ludovic Brenta
  2007-09-18 20:49                 ` Dmitry A. Kazakov
  2007-09-18 21:10               ` Simon Wright
  1 sibling, 1 reply; 74+ messages in thread
From: Ludovic Brenta @ 2007-09-18 19:39 UTC (permalink / raw)


Dmitry A. Kazakov writes:
> On Tue, 18 Sep 2007 20:34:13 +0200, Ludovic Brenta wrote:
>
>> Dmitry A. Kazakov writes:
>>>> On 17 Wrz, 23:07, Ludovic Brenta <ludo...@ludovic-brenta.org> wrote:
>>>> 
>>>>> No, I meant C++ does not have a notation for Ada's concept of a class.
>>>
>>> Neither Ada has (I mean a "notation of concept" (:-)). But T& is
>>> close to T'Class, and IMO the concept of a class as a set of types
>>> [obtained upon inheritance] and a type to represent that set is same
>>> in both languages.  Ada separates the set and its representative,
>>> C++ does not.
>> 
>> I'm not following you.  Do you mean to say that T'Class is not a
>> notation for Ada's concept of a class?
>
> Yes. IMO, concepts do not have notations, maybe in category theory, but not
> in a programming language [*]. In Ada T'Class *denotes* a concrete type. It
> does not denote the concepts of type, of class, of computing, of late
> binding etc.
>
> In a concrete program T'Class may express a concept in the application
> domain space. The latter an OOA theologian would call "class." (Did you
> mean this?) They argue that there exists some inherent objective relation
> between dogs (canis lupus familiaris), the word "dog," and Dog'Class. That
> IMO is beyond the language Ada, and is a purely religious concept. (Sorry
> for a pun (:-))

Call me religious all you want.  I thing your reasoning is wrong.  In
human languages there are words to denote concepts, and I think
T'Class denotes the concept of "the tagged type T or any type derived
therefrom", i.e. the Ada concept of a class.  T'Class is not a
specific type, it is a class-wide type.

So, I disagree with your earlier statement too.

But I'm not going to go into a theological argument with anyone.

-- 
Ludovic Brenta.



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

* Re: Inherited Methods and such
  2007-09-18 15:25         ` Dmitry A. Kazakov
  2007-09-18 18:34           ` Ludovic Brenta
@ 2007-09-18 20:39           ` Maciej Sobczak
  2007-09-18 21:12             ` Dmitry A. Kazakov
  1 sibling, 1 reply; 74+ messages in thread
From: Maciej Sobczak @ 2007-09-18 20:39 UTC (permalink / raw)


On 18 Wrz, 17:25, "Dmitry A. Kazakov" <mail...@dmitry-kazakov.de>
wrote:

> > Then I'll pick that latter option. There is no pointer/reference to a
> > specific type in C++.
>
> "this" within a constructor/destructor?

The dynamic type of the object is then equal to its static type (the
dynamic type of the object *changes* during the process of
construction/destruction).

Still, there is no pointer/reference to a specific type in C++. The
this pointer always points to the current object, and calls to virtual
functions via this pointer are always dispatching.

There is *nothing* special about 'this' in constructor/destructor.
There is, however, something special about the relation between
dynamic and static type of the object.

Where's the difference? You can call some regular method from the
constructor/destructor and then, in that method, the 'this' pointer is
subject to regular rules - there is no tracking on where the pointer
comes from when the method is called. In other words, there is no such
a thing as "this within a constructor/destructor", because the static
type of "this" pointer and its abilities are always the same.
The only thing that is special is the dynamic type of the object,
which changes over the lifetime of the object.

> A pointer variable pointing to an object under construction/destruction?

Same. Static type is always the same. There is nothing special about
the pointer.
It's the dynamic type of the object that changes.

> I
> don't know how the C++ standard defines this, as implementation-dependent,
> or else as erroneous or else as "specific" (in Ada terms).

Neither of these.

--
Maciej Sobczak
http://www.msobczak.com/




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

* Re: Inherited Methods and such
  2007-09-18 19:39               ` Ludovic Brenta
@ 2007-09-18 20:49                 ` Dmitry A. Kazakov
  0 siblings, 0 replies; 74+ messages in thread
From: Dmitry A. Kazakov @ 2007-09-18 20:49 UTC (permalink / raw)


On Tue, 18 Sep 2007 21:39:13 +0200, Ludovic Brenta wrote:

> Call me religious all you want.  I thing your reasoning is wrong.  In
> human languages there are words to denote concepts,

Natural languages are non-formal.

> and I think
> T'Class denotes the concept of "the tagged type T or any type derived
> therefrom", i.e. the Ada concept of a class.

Is there a difference between a type and a concept of?

BTW, class as an OOP concept is not "the tagged type T or any type derived
therefrom," because T is irrelevant in the concept of class. But our
T'Class refers to a *concrete* class rooted in nothing, but T. You cannot
say that 12 is a concept of number. At most, a Platonist could argue that
12 denotes a concept of twelveness, and that "concept" denotes
"conceptness," while "conceptness" denotes, ...? (:-))

> T'Class is not a specific type, it is a class-wide type.

Yet it is a type, and T'Class denotes nothing but that type. So:

   X : T'Class := ...

declares a variable X of a type, not of a concept. 

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



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

* Re: Inherited Methods and such
  2007-09-18 19:29             ` Dmitry A. Kazakov
  2007-09-18 19:39               ` Ludovic Brenta
@ 2007-09-18 21:10               ` Simon Wright
  1 sibling, 0 replies; 74+ messages in thread
From: Simon Wright @ 2007-09-18 21:10 UTC (permalink / raw)


"Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> writes:

> The latter an OOA theologian would call "class." (Did you mean
> this?) They argue that there exists some inherent objective relation
> between dogs (canis lupus familiaris), the word "dog," and
> Dog'Class.

These seem to me to be different modelling levels, and suggesting
there might be a relationship of that sort between them would be a
category error.

None of the OOA heroes I know would make such a mistake.

Dog'Class is clearly an expression in an Ada-based platform-specific
model; at the platform-independent level Dog might have all sorts of
different connotations depending on what domain was being
modelled. 'Dog' (itself, and in its relationships to other classes in
the domain) would be quite different between say a model for a
veterinary practice and one for a police drug squad.



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

* Re: Inherited Methods and such
  2007-09-18 20:39           ` Maciej Sobczak
@ 2007-09-18 21:12             ` Dmitry A. Kazakov
  2007-09-19 14:49               ` Maciej Sobczak
  0 siblings, 1 reply; 74+ messages in thread
From: Dmitry A. Kazakov @ 2007-09-18 21:12 UTC (permalink / raw)


On Tue, 18 Sep 2007 13:39:25 -0700, Maciej Sobczak wrote:

> On 18 Wrz, 17:25, "Dmitry A. Kazakov" <mail...@dmitry-kazakov.de>
> wrote:
> 
>>> Then I'll pick that latter option. There is no pointer/reference to a
>>> specific type in C++.
>>
>> "this" within a constructor/destructor?
> 
> The dynamic type of the object is then equal to its static type (the
> dynamic type of the object *changes* during the process of
> construction/destruction).

Equivalently you could say that the object's type mutates upon dispatch,
and thus there is no specific types in Ada. These views are complementary.
As long as you don't distinguish T and T'Class you cannot tell what is
specific and what is not, and are forced to invent imaginary "static" and
"dynamic" substitute types.

But from behavioral point of view, the type is just equivalent to the
behavior of its values. If an object behaves as if it had a specific type,
then it just has that type, because there is no way to prove otherwise (by
means of writing a legal program capable to determine a difference).

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



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

* Re: Inherited Methods and such
  2007-09-18 21:12             ` Dmitry A. Kazakov
@ 2007-09-19 14:49               ` Maciej Sobczak
  2007-09-19 15:16                 ` Dmitry A. Kazakov
  0 siblings, 1 reply; 74+ messages in thread
From: Maciej Sobczak @ 2007-09-19 14:49 UTC (permalink / raw)


On 18 Wrz, 23:12, "Dmitry A. Kazakov" <mail...@dmitry-kazakov.de>
wrote:

> But from behavioral point of view, the type is just equivalent to the
> behavior of its values. If an object behaves as if it had a specific type,
> then it just has that type

Yes. What if that behaviour changes over time? Would you say that the
type changes over time as well?
This is what justifies the concept of "dynamic type".

--
Maciej Sobczak
http://www.msobczak.com/




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

* Re: Inherited Methods and such
  2007-09-19 14:49               ` Maciej Sobczak
@ 2007-09-19 15:16                 ` Dmitry A. Kazakov
  2007-09-19 22:13                   ` Maciej Sobczak
  0 siblings, 1 reply; 74+ messages in thread
From: Dmitry A. Kazakov @ 2007-09-19 15:16 UTC (permalink / raw)


On Wed, 19 Sep 2007 07:49:36 -0700, Maciej Sobczak wrote:

> On 18 Wrz, 23:12, "Dmitry A. Kazakov" <mail...@dmitry-kazakov.de>
> wrote:
> 
>> But from behavioral point of view, the type is just equivalent to the
>> behavior of its values. If an object behaves as if it had a specific type,
>> then it just has that type
> 
> Yes. What if that behaviour changes over time? Would you say that the
> type changes over time as well?

It cannot. Behavior is time-invariant, because it summarizes our
expectance. If our expectance is wrong, then the program is incorrect, and
reverse.

> This is what justifies the concept of "dynamic type".

No it does not. How does a dynamic type behave? The answer is - its objects
dispatch in this an that way. The description of how they do this is the
behavior, and it does not change... Or else it is not a well-typed language
[that is in case when T'Class acts as if it were T, because it is not yet
constructed, or whatever excuse one might use.]

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



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

* Re: Inherited Methods and such
  2007-09-19 15:16                 ` Dmitry A. Kazakov
@ 2007-09-19 22:13                   ` Maciej Sobczak
  2007-09-20  8:12                     ` Dmitry A. Kazakov
  0 siblings, 1 reply; 74+ messages in thread
From: Maciej Sobczak @ 2007-09-19 22:13 UTC (permalink / raw)


On 19 Wrz, 17:16, "Dmitry A. Kazakov" <mail...@dmitry-kazakov.de>
wrote:

> > Yes. What if that behaviour changes over time? Would you say that the
> > type changes over time as well?
>
> It cannot. Behavior is time-invariant, because it summarizes our
> expectance.

I don't like this kind of discussions; they lead nowhere. You try to
force others into your own assumptions as if they were general axioms.

My expectations are different during the lifetime of the object. Hence
- the behavior that I expect is time-variant. Hence - the type changes
over time. End of story.

> If our expectance is wrong, then the program is incorrect, and
> reverse.

Of course. But actually my expectance is right and my programs are
correct.

> > This is what justifies the concept of "dynamic type".
>
> No it does not. How does a dynamic type behave?

Dynamically.

--
Maciej Sobczak
http://www.msobczak.com/




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

* Re: Inherited Methods and such
  2007-09-19 22:13                   ` Maciej Sobczak
@ 2007-09-20  8:12                     ` Dmitry A. Kazakov
  2007-09-20 13:52                       ` Maciej Sobczak
  0 siblings, 1 reply; 74+ messages in thread
From: Dmitry A. Kazakov @ 2007-09-20  8:12 UTC (permalink / raw)


On Wed, 19 Sep 2007 15:13:06 -0700, Maciej Sobczak wrote:

> On 19 Wrz, 17:16, "Dmitry A. Kazakov" <mail...@dmitry-kazakov.de>
> wrote:
> 
> My expectations are different during the lifetime of the object. Hence
> - the behavior that I expect is time-variant. Hence - the type changes
> over time. End of story.

If you say that an object of the type T1 can take values of the type T2,
such that not T1<:T2, then either:

A. The language is untyped, as you cannot name the object's type, or else

B. There exists a type T3, such that T1<:T3 and T2<:T3, (with the domain
set containing both the value before and the value after change). This
third type of the object does not change, and the type of the object is
neither T1, nor T2, but T3.

>>> This is what justifies the concept of "dynamic type".
>>
>> No it does not. How does a dynamic type behave?
> 
> Dynamically.

Huh, let us take this and consider the predicate "dynamically". Then:

   forall x of T, dynamically(x)

I.e. dynamically(T) is invariant.

There is always a common denominator of the behavior for any per-design
identified set of values associated with an object. In a typed language
this and only this behavior is described by the object's type. For this
reason any object has exactly one type. No more, no less.

One can argue for alternative notions of type and typed language, but they
likely would not be consistent with behavioral point of view, which you
have already subscribed to, or?

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



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

* Re: Inherited Methods and such
  2007-09-20  8:12                     ` Dmitry A. Kazakov
@ 2007-09-20 13:52                       ` Maciej Sobczak
  2007-09-20 16:22                         ` Dmitry A. Kazakov
  0 siblings, 1 reply; 74+ messages in thread
From: Maciej Sobczak @ 2007-09-20 13:52 UTC (permalink / raw)


On 20 Wrz, 10:12, "Dmitry A. Kazakov" <mail...@dmitry-kazakov.de>
wrote:

> > My expectations are different during the lifetime of the object. Hence
> > - the behavior that I expect is time-variant. Hence - the type changes
> > over time. End of story.
>
> If you say that an object of the type T1 can take values of the type T2,
> such that not T1<:T2, then either:

What is T1<:T2 ?

Whatever it is, your assumptions are still wrong.
There is no point in discussing the concept of "taking the value"
here, because the value is meaningful in the context of its type only.
The object under construction (in C++) changes its dynamic type *at
the same time* acquiring new components. This is crucial. The object
acquires new components while it traverses the constructor chain and
these new components obviously take part in value interpretation of
the whole.
There is no such thing as object of type T taking new value, nor the
value changing type. Neither value nor type is constant here. They
flow together as the object is progressively constructed/destructed
and every single moment in time the object has a valid value of *some*
type.

> Huh, let us take this and consider the predicate "dynamically". Then:
>
>    forall x of T, dynamically(x)
[...]

> For this
> reason any object has exactly one type. No more, no less.

Yes. Every single moment the object has one type. But the time is
passing and the object's classification flows together with time. This
can be observed with dispatching operations.

> One can argue for alternative notions of type and typed language, but they
> likely would not be consistent with behavioral point of view, which you
> have already subscribed to, or?

Or we get stuck with the idea that either T or value must be
invariant.
This is not true in C++.

--
Maciej Sobczak
http://www.msobczak.com/




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

* Re: Inherited Methods and such
  2007-09-20 13:52                       ` Maciej Sobczak
@ 2007-09-20 16:22                         ` Dmitry A. Kazakov
  2007-09-20 20:45                           ` Maciej Sobczak
  0 siblings, 1 reply; 74+ messages in thread
From: Dmitry A. Kazakov @ 2007-09-20 16:22 UTC (permalink / raw)


On Thu, 20 Sep 2007 06:52:33 -0700, Maciej Sobczak wrote:

> On 20 Wrz, 10:12, "Dmitry A. Kazakov" <mail...@dmitry-kazakov.de>
> wrote:
> 
>>> My expectations are different during the lifetime of the object. Hence
>>> - the behavior that I expect is time-variant. Hence - the type changes
>>> over time. End of story.
>>
>> If you say that an object of the type T1 can take values of the type T2,
>> such that not T1<:T2, then either:
> 
> What is T1<:T2 ?

Subsumption.

> Whatever it is, your assumptions are still wrong.
> There is no point in discussing the concept of "taking the value"
> here, because the value is meaningful in the context of its type only.

Even better, so objects have no values, but many types...

[...]
> There is no such thing as object of type T taking new value, nor the
> value changing type. Neither value nor type is constant here. They
> flow together as the object is progressively constructed/destructed
> and every single moment in time the object has a valid value of *some*
> type.

Right, this is called untyped. The rest is handwaving.

>> Huh, let us take this and consider the predicate "dynamically". Then:
>>
>>    forall x of T, dynamically(x)
> [...]
> 
>> For this
>> reason any object has exactly one type. No more, no less.
> 
> Yes. Every single moment the object has one type. But the time is
> passing and the object's classification flows together with time.

Nope, the result is another object, *because* you cannot change the type.
Even if you cast and per chance the address of the result is same (why
should anybody care?), it is a different object. Otherwise, you would have
to drop the concept of single type and go untyped.

(BTW classification. "Type" is another word for "classification." Types
classify sets of values)

> This is not true in C++.

It is no matter how C++, Ada or PHP would define it. Because it is a model
that describes what is going on in the given language. Here the programming
language is just an object language, a subject. A model is only wrong when
it describes things incorrectly. If C++ calls type "class," that does not
bother me, naming conventions do not influence the model. Ada subtypes and
Liskov subtypes as known in CS have nothing in common either. So what?

The model I am talking about can describe C++, though it cannot describe it
in a way that some nice and desirable constraints, like object-type
bijection, statically determinable types, statically comparable types,
would necessarily hold. This classifies C++ as weakly typed, if not
untyped. Is that surprising?

The choice of a model is a question of personal preferences. I don't like
models where an object has many types but no values. It is
counterintuitive, and I believe inconsistent. Provided, somebody would take
care and tried to formalize such a mess.

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



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

* Re: Inherited Methods and such
  2007-09-20 16:22                         ` Dmitry A. Kazakov
@ 2007-09-20 20:45                           ` Maciej Sobczak
  2007-09-21 18:59                             ` Dmitry A. Kazakov
  0 siblings, 1 reply; 74+ messages in thread
From: Maciej Sobczak @ 2007-09-20 20:45 UTC (permalink / raw)


On 20 Wrz, 18:22, "Dmitry A. Kazakov" <mail...@dmitry-kazakov.de>
wrote:

> >> If you say that an object of the type T1 can take values of the type T2,
> >> such that not T1<:T2, then either:
>
> > What is T1<:T2 ?
>
> Subsumption.

This I can guess. What is the direction?

> > Whatever it is, your assumptions are still wrong.
> > There is no point in discussing the concept of "taking the value"
> > here, because the value is meaningful in the context of its type only.
>
> Even better, so objects have no values, but many types...

No. Object has one value.

> > There is no such thing as object of type T taking new value, nor the
> > value changing type. Neither value nor type is constant here. They
> > flow together as the object is progressively constructed/destructed
> > and every single moment in time the object has a valid value of *some*
> > type.
>
> Right, this is called untyped. The rest is handwaving.

No, this is strongly typed, because the type is determined
declaratively and can be deduced without examining the value. The
value has to always match the type, which is known a priori.

But I agree that there is some handwaving in this discussion.

> > Every single moment the object has one type. But the time is
> > passing and the object's classification flows together with time.
>
> Nope, the result is another object, *because* you cannot change the type.

It is always the same object, on the basis of its identifier, which is
invariant.
By indentifier I take the address of the object.

And yes, the type *changes* while the object traverses along the chain
of constructions/destructions.

> Even if you cast and per chance the address of the result is same (why
> should anybody care?), it is a different object.

No, it's the same object. It's just fattening.
Unless in your world people change their identity after fattening. :-)

> Otherwise, you would have
> to drop the concept of single type and go untyped.

If you limit yourself to these two options only, it's your limitation,
not mine.
I see a place for dynamically changing types. It is *single* in any
given moment. It is still typed, because it is determined a priori,
not by actual value. I *know* how the type will change without looking
at the value. It is completely different from assigning whatever value
to some object and saying that it changed its type as a result of
this. This is the point that you seem to be missing.


--
Maciej Sobczak
http://www.msobczak.com/




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

* Re: Inherited Methods and such
  2007-09-20 20:45                           ` Maciej Sobczak
@ 2007-09-21 18:59                             ` Dmitry A. Kazakov
  2007-09-21 21:02                               ` Maciej Sobczak
  0 siblings, 1 reply; 74+ messages in thread
From: Dmitry A. Kazakov @ 2007-09-21 18:59 UTC (permalink / raw)


On Thu, 20 Sep 2007 13:45:19 -0700, Maciej Sobczak wrote:

> On 20 Wrz, 18:22, "Dmitry A. Kazakov" <mail...@dmitry-kazakov.de>
> wrote:
> 
>>>> If you say that an object of the type T1 can take values of the type T2,
>>>> such that not T1<:T2, then either:
>>
>>> What is T1<:T2 ?
>>
>> Subsumption.
> 
> This I can guess. What is the direction?

Any value of T1 is also one of T2.

> No, this is strongly typed, because the type is determined
> declaratively and can be deduced without examining the value.

If it were so, then

class T
{
   T () { f(); g(); }
   void g() {}
   virtual void f() {}
};

should be illegal. Consider f() called in the constructor. The hidden
parameter *this of is neither of T nor of T'Class:

It cannot be of T, because T is not yet constructed (and you don't want to
consider *this specific anyway). It is not of T'Class because T and all
possible derived from it types shall be constructed before T'Class. In
fact, neither g(), nor f() can be called in a true constructor.

BTW, this is also the reason why people calling Ada's Initialize a
constructor are wrong. Initialize is not a constructor of either T or
T'Class.

Further, the type of *this is indeterminable, because I can explicitly call
the constructor at any stage of filling the dispatching table, the effect
will depend on the table. So either you commit *this to a mutating specific
type T or to static polymorphic type T'Class. But in either case the
contract is violated in T::T().

>>> Every single moment the object has one type. But the time is
>>> passing and the object's classification flows together with time.
>>
>> Nope, the result is another object, *because* you cannot change the type.
> 
> It is always the same object, on the basis of its identifier, which is
> invariant. By indentifier I take the address of the object.

1. You cannot take address because the object might be in cache.
2. If the identifier is the only permanent property of an object then it is
exactly what one understand under untyped. The only behavior you can
associate with the object is its identifier.

>> Even if you cast and per chance the address of the result is same (why
>> should anybody care?), it is a different object.
> 
> No, it's the same object. It's just fattening.
> Unless in your world people change their identity after fattening. :-)

In my world people don't become whales upon fattening. At least not in one
generation. (:-))

>> Otherwise, you would have
>> to drop the concept of single type and go untyped.
> 
> If you limit yourself to these two options only, it's your limitation,
> not mine.
> I see a place for dynamically changing types. It is *single* in any
> given moment.

> It is still typed, because it is determined a priori,
> not by actual value.

See above.

> I *know* how the type will change without looking
> at the value.

If you *know* it, then the actual type of the object is a class containing
the trace of all possible types the object would "take." In this case the
object is polymorphic and has exactly one type. The construction is the
following:

T={t1,t2,t3}
S={s1,s2}

Now if you know that X is either T or S. Then the type of X is

class {T,S}= { (T,t1), (T,t2), (T,t3), (S,s1), (S,s2) }

X does not mutate and its values are neither of T or of S, we cannot do
that without breaking strong typing.

> It is completely different from assigning whatever value
> to some object and saying that it changed its type as a result of
> this.

How is it different? Why the type can be changed upon construction, but
cannot upon assignment? Why not to demote/promote?

In Ada terms it would change the tag. It is difficult to implement but
consistent, under the assumption, that you do not change the type! The type
is T'Class. In the example above:

   X : class {T,S} := (T,t1);
begin
   X := (T,t2);
   X := (S,s1);  -- The type is not changed!

The contract of X is invariantly class {T,S}. The so-called specific type
of X changes, but it is not the true type of. It is the type of the second
component of its value. The true type is class {T,S}.

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



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

* Re: Inherited Methods and such
  2007-09-21 18:59                             ` Dmitry A. Kazakov
@ 2007-09-21 21:02                               ` Maciej Sobczak
  2007-09-22  8:48                                 ` Dmitry A. Kazakov
  0 siblings, 1 reply; 74+ messages in thread
From: Maciej Sobczak @ 2007-09-21 21:02 UTC (permalink / raw)


On 21 Wrz, 20:59, "Dmitry A. Kazakov" <mail...@dmitry-kazakov.de>
wrote:

> >>>> If you say that an object of the type T1 can take values of the type T2,
> >>>> such that not T1<:T2, then either:
>
> >>> What is T1<:T2 ?
>
> >> Subsumption.
>
> > This I can guess. What is the direction?
>
> Any value of T1 is also one of T2.

OK. But then your initial usage of it (still cited above) is wrong in
case of constructor, because the type changes in the "more specific"
direction.

> > No, this is strongly typed, because the type is determined
> > declaratively and can be deduced without examining the value.
>
> If it were so, then
>
> class T
> {
>    T () { f(); g(); }
>    void g() {}
>    virtual void f() {}
>
> };
>
> should be illegal. Consider f() called in the constructor. The hidden
> parameter *this of is neither of T nor of T'Class:
>
> It cannot be of T, because T is not yet constructed (and you don't want to
> consider *this specific anyway).

Not exactly. In the constructor of T, *this already has type T.
Note that f() is called *after* initializing the fields (the
constructor initializer list serves some purpose!). Some invariants
might not be established yet, but the fields are supposedly already
initialized. The invariants are not an issue - functions calling each
other have to deal with this effect in general.

> It is not of T'Class

It is - T belongs to T'Class.

> In
> fact, neither g(), nor f() can be called in a true constructor.

No. Both can be called, because *this has type T. Both static and
dynamic.
This is actually the case where virtual calls can be inlined.

> BTW, this is also the reason why people calling Ada's Initialize a
> constructor are wrong.

Right. Ada doesn't have *any* constructors. :-)

> Further, the type of *this is indeterminable, because I can explicitly call
> the constructor

No, you cannot. Constructor is not a function - you cannot just "call"
it.
(So I cut out your wrong conclusions.)

> >>> Every single moment the object has one type. But the time is
> >>> passing and the object's classification flows together with time.
>
> >> Nope, the result is another object, *because* you cannot change the type.
>
> > It is always the same object, on the basis of its identifier, which is
> > invariant. By indentifier I take the address of the object.
>
> 1. You cannot take address because the object might be in cache.

What is "cache"? The C++ standard says nothing about it.
I can take the address of an object whenever I want.

> 2. If the identifier is the only permanent property of an object then it is
> exactly what one understand under untyped.

Which consistently means that you just got stuck with the idea that
type must be permanent.

Identifier is permanent and this allows me to say that the object
*persists* along the process (there is no object replacement, etc.).
The type evolves during this process.

> In my world people don't become whales upon fattening. At least not in one
> generation. (:-))

Good. :-)

What about growing up?
What about getting promoted in the company?

> > I *know* how the type will change without looking
> > at the value.
>
> If you *know* it, then the actual type of the object is a class containing
> the trace of all possible types the object would "take."

No. There is nothing that forces me to take this view. I don't need
any "actual" type, becuase the actual type is determined by the
progress along the inheritance chain.

> > It is completely different from assigning whatever value
> > to some object and saying that it changed its type as a result of
> > this.
>
> How is it different? Why the type can be changed upon construction, but
> cannot upon assignment?

Because the language recognizes the fact that construction/destruction
does not happen immediately and it is a "build-up" process. Assignment
is performed on the object that is already "established".

> Why not to demote/promote?

There are languages that support this.

> In Ada terms it would change the tag.

Yes.

> It is difficult to implement but
> consistent, under the assumption, that you do not change the type! The type
> is T'Class.

You can end up with Java-like Object'Class. Not much helpful.

I prefer the model offered by C++, because it allows the analysis of
code to be more reasonable. The objects have invariant types between
the end of their constructor and the beginning of their destructor. So
- a static set of objects exposes the property of static type system.
It's only the process of birth and death that is difficult and it is
when C++ prevents the programmer from dispatching to the function that
expects some fields to be initialized while they are not. I have never
found myself in a situation where that would make any difference (I
don't call virtual functions from constructors), but I find the model
to be more complete just by knowing that the problem can never happen.
Correctness by construction, anyone? ;-)

--
Maciej Sobczak
http://www.msobczak.com/




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

* Re: Inherited Methods and such
  2007-09-21 21:02                               ` Maciej Sobczak
@ 2007-09-22  8:48                                 ` Dmitry A. Kazakov
  2007-09-22 21:53                                   ` Maciej Sobczak
  2007-09-25  1:59                                   ` Randy Brukardt
  0 siblings, 2 replies; 74+ messages in thread
From: Dmitry A. Kazakov @ 2007-09-22  8:48 UTC (permalink / raw)


On Fri, 21 Sep 2007 14:02:06 -0700, Maciej Sobczak wrote:

> On 21 Wrz, 20:59, "Dmitry A. Kazakov" <mail...@dmitry-kazakov.de>
> wrote:
> 
>>>>>> If you say that an object of the type T1 can take values of the type T2,
>>>>>> such that not T1<:T2, then either:
>>
>>>>> What is T1<:T2 ?
>>
>>>> Subsumption.
>>
>>> This I can guess. What is the direction?
>>
>> Any value of T1 is also one of T2.
> 
> OK. But then your initial usage of it (still cited above) is wrong in
> case of constructor, because the type changes in the "more specific"
> direction.

It is not "more specific." Just note the name, it is called programming "by
extension" in both Ada and C++. If that were more specific, then C++ would
have no problem with calling virtual functions from there.

>>> No, this is strongly typed, because the type is determined
>>> declaratively and can be deduced without examining the value.
>>
>> If it were so, then
>>
>> class T
>> {
>>    T () { f(); g(); }
>>    void g() {}
>>    virtual void f() {}
>>
>> };
>>
>> should be illegal. Consider f() called in the constructor. The hidden
>> parameter *this of is neither of T nor of T'Class:
>>
>> It cannot be of T, because T is not yet constructed (and you don't want to
>> consider *this specific anyway).
> 
> Not exactly. In the constructor of T, *this already has type T.

But you objected that *this is specific. You claimed that it dispatches in
f(), if it does then it is of T'Class.

> Note that f() is called *after* initializing the fields (the
> constructor initializer list serves some purpose!). Some invariants
> might not be established yet, but the fields are supposedly already
> initialized. The invariants are not an issue - functions calling each
> other have to deal with this effect in general.

"Fields" refer to an implementation detail or else to some other type, like
struct { <same-fields> }. T has more than just fields, it not struct {...}.

When you claim that T has been constructed prior the body of T(), then the
latter simply cannot be a constructor of T! Otherwise, you may not call
methods of T from its body. 

It is plainly inconsistent.

>> It is not of T'Class
> 
> It is - T belongs to T'Class.

If you have a wheel from a car that does not mean you can turn the motor.

If it were T'Class then f() would function properly as it shall do on the
class. But it does not. It exposes a different behavior, hence, either it
has a different type, or else the language is wrong. [In fact it is both
(:-))]

>> BTW, this is also the reason why people calling Ada's Initialize a
>> constructor are wrong.
> 
> Right. Ada doesn't have *any* constructors. :-)

Surely it has. The point is that in NO typed language a constructor can be
defined solely in the language terms. Constructors have to be generated
"per magic." But the language shall provide hooks for user-defined
insertions in the generated constructors at the points, where the type
contracts are satisfied. Isn't it obvious?

Ada keeps on trying to use only magic. Returning limited types, new in Ada
2005, is just another step in this direction. This is IMO a road to
nowhere. And also, it is inconsistent with Ada's own stance on types
matching by-name rather than by-structure. Surprisingly, but C++ took the
opposite direction, alas its construction model is inconsistent.

>> Further, the type of *this is indeterminable, because I can explicitly call
>> the constructor
> 
> No, you cannot. Constructor is not a function - you cannot just "call"
> it.

You mean that ptr->T::T() is illegal in the current incarnation of C++? But
that's no matter.

> (So I cut out your wrong conclusions.)

Don't hurry, I can pass this as parameter:

   T() : A(this), B(this), C(this)  {...}

and enjoy all the mess. The point is, this may appear only after },
otherwise, the type system leaks.

>> 2. If the identifier is the only permanent property of an object then it is
>> exactly what one understand under untyped.
> 
> Which consistently means that you just got stuck with the idea that
> type must be permanent.
> 
> Identifier is permanent and this allows me to say that the object
> *persists* along the process (there is no object replacement, etc.).
> The type evolves during this process.

What does persist? What is an object? You said it has a name. I am excited,
but it does not help me in any way. What can I do with the object through
its wonderful name? If you say that I can take its address, then that
means: the type (behavior) has the function &. If you say that I cannot
take its address, then the type does not have &. Either or. You cannot have
conditionally legal methods in a statically typed language.

>> In my world people don't become whales upon fattening. At least not in one
>> generation. (:-))
> 
> Good. :-)
> 
> What about growing up?

The type is Human. Height is an attribute of.

> What about getting promoted in the company?

The type is Employee. Rank is an attribute of. Your employer, should know
that you are a "promotable thing" in order to promote you. Otherwise, he is
risking to assign an ash-tray as a project manager.

>>> I *know* how the type will change without looking
>>> at the value.
>>
>> If you *know* it, then the actual type of the object is a class containing
>> the trace of all possible types the object would "take."
> 
> No. There is nothing that forces me to take this view.

Typing does.

> I don't need
> any "actual" type, becuase the actual type is determined by the
> progress along the inheritance chain.

Actual type /= the type. The type = a common denominator of the behavior of
"all objects like this." When you say that the behavior changes in a way
the you cannot identify anything common, then it is "strongly" untyped [and
absolutely useless too]. If you say that you can determine something like
"actual" type, then it is "weakly untyped." Any such system can be made
"strongly typed," by introducing methods like <dynamic_cast>. Here
<dynamic_cast> is defined on the true type. "Actual type" is just the type
of the result of the method <dynamic_cast> and it yields *another* object.
In cannot be the same object, because the types differs. If they differ
then equivalently there exists a method you can apply to one, but not to
another. For this method you cannot say if it were legal to apply it to the
object that has both types.

>>> It is completely different from assigning whatever value
>>> to some object and saying that it changed its type as a result of
>>> this.
>>
>> How is it different? Why the type can be changed upon construction, but
>> cannot upon assignment?
> 
> Because the language recognizes the fact that construction/destruction
> does not happen immediately and it is a "build-up" process. Assignment
> is performed on the object that is already "established".

You introduce many new terms: "immediately," "built-in,"  "established."
Why these should be relevant to a type system? Do we need such complexity?

And what does prevent you from accepting the proposition that no object
exists prior its construction?

>> It is difficult to implement but
>> consistent, under the assumption, that you do not change the type! The type
>> is T'Class.
> 
> You can end up with Java-like Object'Class. Not much helpful.

I am not sure what you mean here. If you mean a common ancestor, then it is
irrelevant. It is the programmer's choice in a language which types algebra
has operations other than only inheritance. Consider Ada's:

   type Int is new Integer;
 
There is no class of Int and Integer, so you cannot have any objects from
that class.

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



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

* Re: Inherited Methods and such
  2007-09-22  8:48                                 ` Dmitry A. Kazakov
@ 2007-09-22 21:53                                   ` Maciej Sobczak
  2007-09-23  8:41                                     ` Dmitry A. Kazakov
  2007-09-25  1:59                                   ` Randy Brukardt
  1 sibling, 1 reply; 74+ messages in thread
From: Maciej Sobczak @ 2007-09-22 21:53 UTC (permalink / raw)


On 22 Wrz, 10:48, "Dmitry A. Kazakov" <mail...@dmitry-kazakov.de>
wrote:

> > OK. But then your initial usage of it (still cited above) is wrong in
> > case of constructor, because the type changes in the "more specific"
> > direction.
>
> It is not "more specific." Just note the name, it is called programming "by
> extension"

This name does not matter much to me. Something can be more specific
and require additional state to provide this more specific behavior.

> >> class T
> >> {
> >>    T () { f(); g(); }
> >>    void g() {}
> >>    virtual void f() {}
>
> >> };

> > Not exactly. In the constructor of T, *this already has type T.
>
> But you objected that *this is specific. You claimed that it dispatches in
> f(), if it does then it is of T'Class.

This concept has no meaning there. *this is of type T and dispatches
to T::_().


> When you claim that T has been constructed prior the body of T(), then the
> latter simply cannot be a constructor of T!

It is a constructor, because the initializer list is part of T::T().

To be strict, object is considered to be constructed *after* the
constructor executes to the end (without an exception), so formally
the object is not yet constructed when T::T() is executing its body.
The point is that in the constructor's body the components (fields)
are already constructed (the initializer list handles this) and this
fact makes it safe to use these fields.
This is important - you can *use* the fields, because they were
already constructed.
It would not be possible if *this dispatched to Derived::f(), because
there the Derived's fields do not yet exist.

> It is plainly inconsistent.

It is consistent - the dispatch will never trick you into using not-
yet-constructed fields.


> If it were T'Class then f() would function properly as it shall do on the
> class. But it does not. It exposes a different behavior, hence, either it
> has a different type, or else the language is wrong.

You got stuck with the idea that T'Class must be constant.

> The point is that in NO typed language a constructor can be
> defined solely in the language terms. Constructors have to be generated
> "per magic." But the language shall provide hooks for user-defined
> insertions in the generated constructors at the points, where the type
> contracts are satisfied. Isn't it obvious?

Makes sense. Isn't it true in what I describe?

> Ada keeps on trying to use only magic. Returning limited types, new in Ada
> 2005, is just another step in this direction. This is IMO a road to
> nowhere. And also, it is inconsistent with Ada's own stance on types
> matching by-name rather than by-structure. Surprisingly, but C++ took the
> opposite direction, alas its construction model is inconsistent.

I don't see any inconsistency there. The inconsistency can be found
under the assumption that types must be invariant. But if we remove
this assumption, then the inconsistency is gone.

> > Constructor is not a function - you cannot just "call"
> > it.
>
> You mean that ptr->T::T() is illegal in the current incarnation of C++?

Exactly.

> Don't hurry, I can pass this as parameter:
>
>    T() : A(this), B(this), C(this)  {...}
>
> and enjoy all the mess.

Yes. You can remove this by prohibiting the use of 'this' in the
initializer list (some compilers warn about it anyway).
It is formally allowed, because can be useful if handled with enough
care.
Kind of Unrestricted_Access.

> > Identifier is permanent and this allows me to say that the object
> > *persists* along the process (there is no object replacement, etc.).
> > The type evolves during this process.
>
> What does persist? What is an object? You said it has a name. I am excited,
> but it does not help me in any way. What can I do with the object through
> its wonderful name? If you say that I can take its address, then that
> means: the type (behavior) has the function &. If you say that I cannot
> take its address, then the type does not have &. Either or. You cannot have
> conditionally legal methods in a statically typed language.

I don't get it. You can call & on the type, unless operator& is
private.
How does it relate to the rest of the discussion, anyway?


> > What about growing up?
>
> The type is Human. Height is an attribute of.

I'm not talking about getting higher, but about growing up.
Human has subclasses Child and Adult. Presumably they can do different
things. :-)

So - what about growing up?

The only way to grow up the child in Ada and C++ is to kill a child
and resurrect it as an adult, reshuffling all necessary references in
the system so that they point to the new entity.
Not funny.

> > What about getting promoted in the company?
>
> The type is Employee. Rank is an attribute of.

Too easy. Employees on different levels on their career paths have
different attributes as well. For example, Managers have lists of
people who report to them. Employees with different contracts have
different attributes related to how their salary is computed. Etc.

Same problem as growing up.


> Actual type /= the type. The type = a common denominator of the behavior of
> "all objects like this." When you say that the behavior changes in a way
> the you cannot identify anything common, then it is "strongly" untyped [and
> absolutely useless too]. If you say that you can determine something like
> "actual" type, then it is "weakly untyped." Any such system can be made
> "strongly typed," by introducing methods like <dynamic_cast>. Here
> <dynamic_cast> is defined on the true type. "Actual type" is just the type
> of the result of the method <dynamic_cast> and it yields *another* object.
> In cannot be the same object, because the types differs. If they differ
> then equivalently there exists a method you can apply to one, but not to
> another. For this method you cannot say if it were legal to apply it to the
> object that has both types.

No. dynamic_cast gives another view on the same object. It does not
give another object.
You talk with someone over the phone. At some point you discover that
it's an adult, then you agree with him (dynamic_cast) that from now on
you can talk like adults. It's still the same person, you just know
more about him.


> > Because the language recognizes the fact that construction/destruction
> > does not happen immediately and it is a "build-up" process. Assignment
> > is performed on the object that is already "established".
>
> You introduce many new terms: "immediately," "built-in,"  "established."
> Why these should be relevant to a type system? Do we need such complexity?

As long as things don't happen "immediately", yes.
Without this "complexity" you get real complexity with dispatching to
vacuum.

> And what does prevent you from accepting the proposition that no object
> exists prior its construction?

Why do you thing it would conflict with anything here?
If you think that it would prevent me from doing anything in the
constructor (because I cannot do anything if there is no object), then
it's wrong - in the constructor I can use already constructed fields.
Even from other functions, which are called by the constructor. It's
after the constructor finishes when the clients can use the object via
its public interface. From their point of view the object exists after
the constructor finished. From the constructor point of view, the
fields exist before the constructor's body is executed. Easy.


--
Maciej Sobczak
http://www.msobczak.com/




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

* Re: Inherited Methods and such
  2007-09-22 21:53                                   ` Maciej Sobczak
@ 2007-09-23  8:41                                     ` Dmitry A. Kazakov
  2007-09-23 20:36                                       ` Maciej Sobczak
  0 siblings, 1 reply; 74+ messages in thread
From: Dmitry A. Kazakov @ 2007-09-23  8:41 UTC (permalink / raw)


On Sat, 22 Sep 2007 14:53:15 -0700, Maciej Sobczak wrote:

> On 22 Wrz, 10:48, "Dmitry A. Kazakov" <mail...@dmitry-kazakov.de>
> wrote:
> 
>>>> class T
>>>> {
>>>>    T () { f(); g(); }
>>>>    void g() {}
>>>>    virtual void f() {}
>>
>>>> };
> 
>>> Not exactly. In the constructor of T, *this already has type T.
>>
>> But you objected that *this is specific. You claimed that it dispatches in
>> f(), if it does then it is of T'Class.
> 
> This concept has no meaning there.

Which concept?

1. The concept of class?
2. The concept of class representative?
3. The concept of typing?

> *this is of type T and dispatches to T::_().

I.e. T is a representative of the class of types which has a polymorphic
subprogram with some name I cannot figure out. The subprogram is such that
its body for some type xxx from the class is "T::_". This description is
language-independent.

>> When you claim that T has been constructed prior the body of T(), then the
>> latter simply cannot be a constructor of T!
> 
> It is a constructor, because the initializer list is part of T::T().

The body of T does not initialize "parts."

> The point is that in the constructor's body the components (fields)
> are already constructed

Yes this is the point.

> (the initializer list handles this) and this
> fact makes it safe to use these fields.

That is irrelevant. Relevant is whether the object is safe to use as T.

> This is important - you can *use* the fields, because they were
> already constructed.

A pile of fields does not make it object. Again, it is either untyped, or
else you are talking about another type: struct {...}. You have constructed
"struct," a predefined container type, you didn't T.

> It would not be possible if *this dispatched to Derived::f(), because
> there the Derived's fields do not yet exist.

= the polymorphic object is not yet constructed
= the class is not yet constructed

One dispatches only on a class (per definition polymorphic subprograms act
on the class)

>> It is plainly inconsistent.
> 
> It is consistent - the dispatch will never trick you into using not-
> yet-constructed fields.

That you cannot run red light on a stool does not make the latter a drive
assistant system.

>> The point is that in NO typed language a constructor can be
>> defined solely in the language terms. Constructors have to be generated
>> "per magic." But the language shall provide hooks for user-defined
>> insertions in the generated constructors at the points, where the type
>> contracts are satisfied. Isn't it obvious?
> 
> Makes sense. Isn't it true in what I describe?

No it isn't, because you refuse to annotate the types involved. When I try
to give an annotation, you answer that this is not C++. The point of
inconsistency is that no annotation can be given.
 
> The inconsistency can be found
> under the assumption that types must be invariant.

Types aren't invariant. You are free to construct new types.

> But if we remove this assumption, then the inconsistency is gone.

No. Assuming you are talking about mutating objects, it is inconsistent
with the notion of typing. You move the mess onto the client, which now
does not know what to expect from the object it deals with. If you
constrain the mutation scale in *any* way, and tell the client about that,
then this would *automatically* define the true type of the object, and all
these fake types you are talking about would be just aberrations.

>> Don't hurry, I can pass this as parameter:
>>
>>    T() : A(this), B(this), C(this)  {...}
>>
>> and enjoy all the mess.
> 
> Yes. You can remove this by prohibiting the use of 'this' in the
> initializer list (some compilers warn about it anyway).
> It is formally allowed, because can be useful if handled with enough
> care.

Good intentions pave the road to hell. But returning back to the issue.
What is the type of *this the constructor of A does observe? Can you name
it?

>>> Identifier is permanent and this allows me to say that the object
>>> *persists* along the process (there is no object replacement, etc.).
>>> The type evolves during this process.
>>
>> What does persist? What is an object? You said it has a name. I am excited,
>> but it does not help me in any way. What can I do with the object through
>> its wonderful name? If you say that I can take its address, then that
>> means: the type (behavior) has the function &. If you say that I cannot
>> take its address, then the type does not have &. Either or. You cannot have
>> conditionally legal methods in a statically typed language.
> 
> I don't get it. You can call & on the type, unless operator& is
> private.

Which type? You said that the only thing objects have for sure is their
names. Period. No types, no operations.

>>> What about growing up?
>>
>> The type is Human. Height is an attribute of.
> 
> I'm not talking about getting higher, but about growing up.
> Human has subclasses Child and Adult. Presumably they can do different
> things. :-)
> 
> So - what about growing up?

The attribute Age.

> The only way to grow up the child in Ada and C++ is to kill a child
> and resurrect it as an adult, reshuffling all necessary references in
> the system so that they point to the new entity.
> Not funny.

But alas real, our cells get replaced as we grow. The point is that there
are two parties in any contract. You cannot consider an object isolated
from the clients of. If your client is a crocodile, then you are meat,
adult or child becomes just a matter of taste...

>> Actual type /= the type. The type = a common denominator of the behavior of
>> "all objects like this." When you say that the behavior changes in a way
>> the you cannot identify anything common, then it is "strongly" untyped [and
>> absolutely useless too]. If you say that you can determine something like
>> "actual" type, then it is "weakly untyped." Any such system can be made
>> "strongly typed," by introducing methods like <dynamic_cast>. Here
>> <dynamic_cast> is defined on the true type. "Actual type" is just the type
>> of the result of the method <dynamic_cast> and it yields *another* object.
>> In cannot be the same object, because the types differs. If they differ
>> then equivalently there exists a method you can apply to one, but not to
>> another. For this method you cannot say if it were legal to apply it to the
>> object that has both types.
> 
> No. dynamic_cast gives another view on the same object. It does not
> give another object.

So you want to introduce views. There are object and views of. Care to
define a difference, in terms *types* of course? (:-))

> You talk with someone over the phone. At some point you discover that
> it's an adult, then you agree with him (dynamic_cast) that from now on
> you can talk like adults. It's still the same person, you just know
> more about him.

Good example. We talk over a communication protocol, which itself is an
object. Yes, there will be another protocol instance after handshaking. Of
course we could design the protocol level as a parameter. But then,
firstly, that would not make it a different type, and secondly, that could
be an unsafe design, as we could forget about the constraint and get an
exception.

>>> Because the language recognizes the fact that construction/destruction
>>> does not happen immediately and it is a "build-up" process. Assignment
>>> is performed on the object that is already "established".
>>
>> You introduce many new terms: "immediately," "built-in,"  "established."
>> Why these should be relevant to a type system? Do we need such complexity?
> 
> As long as things don't happen "immediately", yes.
> Without this "complexity" you get real complexity with dispatching to
> vacuum.

Nope, it is crystal clear in vacuum: no class, no dispatch. Type and only
type have to tell what's going on.

>> And what does prevent you from accepting the proposition that no object
>> exists prior its construction?

[...]
> It's
> after the constructor finishes when the clients can use the object via
> its public interface. From their point of view the object exists after
> the constructor finished. From the constructor point of view, the
> fields exist before the constructor's body is executed. Easy.

= you shall not use any public interface methods from the constructor's
body => C++ is inconsistent allowing this.

----------------
Just annotate types. Fields construction does struct {...}. No less, no
more. You can access fields because struct {...} has been constructed. You
cannot use anything from T / struct {...}, because T is not yet
constructed. (/ denotes set subtraction). The object of struct {...} does
exist. The object T does not. Disagree?

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



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

* Re: Inherited Methods and such
  2007-09-23  8:41                                     ` Dmitry A. Kazakov
@ 2007-09-23 20:36                                       ` Maciej Sobczak
  2007-09-24  9:32                                         ` Dmitry A. Kazakov
  0 siblings, 1 reply; 74+ messages in thread
From: Maciej Sobczak @ 2007-09-23 20:36 UTC (permalink / raw)


On 23 Wrz, 10:41, "Dmitry A. Kazakov" <mail...@dmitry-kazakov.de>
wrote:

> >> But you objected that *this is specific. You claimed that it dispatches in
> >> f(), if it does then it is of T'Class.
>
> > This concept has no meaning there.
>
> Which concept?

T'Class. It has no meaning, because there is no possible family of
types there. There is one type T and everything dispatches to T.
Forget dynamic dispatch where you are in the constructor.

> > The point is that in the constructor's body the components (fields)
> > are already constructed
>
> Yes this is the point.
>
> > (the initializer list handles this) and this
> > fact makes it safe to use these fields.
>
> That is irrelevant. Relevant is whether the object is safe to use as T.

The funny part is that formally there is no such object yet.
Surely you can leak 'this' outside and use the not-yet-existing
object. Whether it is safe depends on what you already managed to do
before leaking it.

In general it is not safe, so don't leak 'this' from inside the
constructor.

> > This is important - you can *use* the fields, because they were
> > already constructed.
>
> A pile of fields does not make it object. Again, it is either untyped, or
> else you are talking about another type: struct {...}. You have constructed
> "struct," a predefined container type, you didn't T.

Yes. Still, there is a lot I can do from the methods of the class,
which understand the state. These methods can even operate before the
invariants of T are fully established. After all, it's their natural
ability.
This is also why it's even more important *not* to dispatch into
vacuum.

> > It would not be possible if *this dispatched to Derived::f(), because
> > there the Derived's fields do not yet exist.
>
> = the polymorphic object is not yet constructed
> = the class is not yet constructed

Exactly. That's what I'm writing about - the type is being built up
*progressively*, not by a magic instant jump from nothing to final
type.

> > It is consistent - the dispatch will never trick you into using not-
> > yet-constructed fields.
>
> That you cannot run red light on a stool does not make the latter a drive
> assistant system.

It's still more preferred than running red light and pretend the
problem does not exist.

> > The inconsistency can be found
> > under the assumption that types must be invariant.
>
> Types aren't invariant. You are free to construct new types.

That's what I'm writing about. What are we discussing, then? :-)

> > But if we remove this assumption, then the inconsistency is gone.
>
> No. Assuming you are talking about mutating objects, it is inconsistent
> with the notion of typing. You move the mess onto the client, which now
> does not know what to expect from the object it deals with.

No. The client doesn't even see the object before it's fully
constructed, so no mess is moved onto him.
The exception is when the pointer to not-yet-constructed object is
leaked to the client, but this is easier to follow and constrain.

Interestingly, the same problem (with leaking the pointer) exists when
the types is always the final one from the start. So I don't
understand why do you think this is any better.

> If you
> constrain the mutation scale in *any* way, and tell the client about that,
> then this would *automatically* define the true type of the object, and all
> these fake types you are talking about would be just aberrations.

That's true. How do you compare C++ with Ada in this context?

> >> Don't hurry, I can pass this as parameter:
>
> >>    T() : A(this), B(this), C(this)  {...}
>
> >> and enjoy all the mess.
>
> > Yes. You can remove this by prohibiting the use of 'this' in the
> > initializer list (some compilers warn about it anyway).
> > It is formally allowed, because can be useful if handled with enough
> > care.
>
> Good intentions pave the road to hell. But returning back to the issue.
> What is the type of *this the constructor of A does observe? Can you name
> it?

It's T. And the only thing A can legally (standard-wise) do with the
given pointer is to *store it*.
I understand your objection that in this case (given the above
constraint) it's not T anymore, but making it formally complete would
bring more practical issues than it theoretically solves. In
particular, A would need to later ask the given pointer for T's view.

And again, Ada doesn't solve it, either.

> > I don't get it. You can call & on the type, unless operator& is
> > private.
>
> Which type? You said that the only thing objects have for sure is their
> names. Period. No types, no operations.

No, I didn't say that. I said that the address (name) persists while
the object moves along the constructor chain. When it comes to
operations, note that the object's type move along the known type
chain. This also allows to predict which operations are available. In
particular (in the case of constructor chain), operations never
disappear, so if you already know some of them at any given stage, you
can keep using them.

> > So - what about growing up?
>
> The attribute Age.

You try to hide now. :-)
Again: Human has two subclasses: Child and Adult.

What about growing up?

> > The only way to grow up the child in Ada and C++ is to kill a child
> > and resurrect it as an adult, reshuffling all necessary references in
> > the system so that they point to the new entity.
> > Not funny.
>
> But alas real, our cells get replaced as we grow.

As far as I know, not in heart tissue. But this is off-topic. :-)

> > No. dynamic_cast gives another view on the same object. It does not
> > give another object.
>
> So you want to introduce views. There are object and views of. Care to
> define a difference, in terms *types* of course? (:-))

View is what you have with references to objects. There is nothing to
introduce here.

In particular, you can have *many views of different types* on *the
same object* at *the same time*. Don't try to hide it under the
carpet, it will pop up anyway. :-)

> > You talk with someone over the phone. At some point you discover that
> > it's an adult, then you agree with him (dynamic_cast) that from now on
> > you can talk like adults. It's still the same person, you just know
> > more about him.
>
> Good example. We talk over a communication protocol, which itself is an
> object. Yes, there will be another protocol instance after handshaking. Of
> course we could design the protocol level as a parameter. But then,
> firstly, that would not make it a different type, and secondly, that could
> be an unsafe design, as we could forget about the constraint and get an
> exception.

And your point is? Mine was that dynamic_cast does not give another
object.
If it does not give another object, what it gives then? A view.

> > Without this "complexity" you get real complexity with dispatching to
> > vacuum.
>
> Nope, it is crystal clear in vacuum: no class, no dispatch. Type and only
> type have to tell what's going on.

I don't get it. If I dispatch to the type where even the fields were
not yet initialized, how I can decide what I can do? Which type should
I ask?

> > It's
> > after the constructor finishes when the clients can use the object via
> > its public interface. From their point of view the object exists after
> > the constructor finished. From the constructor point of view, the
> > fields exist before the constructor's body is executed. Easy.
>
> = you shall not use any public interface methods from the constructor's
> body => C++ is inconsistent allowing this.

Yes. My turn: Ada is insonsistent here as well. And Java, etc.

The difference is that C++ does not dispatch into vacuum.

> ----------------
> Just annotate types. Fields construction does struct {...}. No less, no
> more. You can access fields because struct {...} has been constructed. You
> cannot use anything from T / struct {...}, because T is not yet
> constructed. (/ denotes set subtraction). The object of struct {...} does
> exist. The object T does not. Disagree?

I like it. Really.
Well, not really. You might leak the 'this' pointer (which would be of
type struct {...}) out of the constructor and anybody would be able to
mess with fields, which breaks encapsulation.
Instead of struct {...} I'd prefer:

class T_fields
{
private:
   ... // all fields (or their references) go here

   friend class T;
};

Private fields make it safe to leak the pointer outside (it's useless
outside, so no harm in leaking it), and friend declaration allows
access from T's constructor itself and other involved methods.
Another, maybe simpler, option would be to make the struct {...}
private in T, so it could not be even leaked and no protection is then
necessary.

--
Maciej Sobczak
http://www.msobczak.com/




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

* Re: Inherited Methods and such
  2007-09-23 20:36                                       ` Maciej Sobczak
@ 2007-09-24  9:32                                         ` Dmitry A. Kazakov
  2007-09-24 15:02                                           ` Maciej Sobczak
  0 siblings, 1 reply; 74+ messages in thread
From: Dmitry A. Kazakov @ 2007-09-24  9:32 UTC (permalink / raw)


On Sun, 23 Sep 2007 13:36:45 -0700, Maciej Sobczak wrote:

> On 23 Wrz, 10:41, "Dmitry A. Kazakov" <mail...@dmitry-kazakov.de>
> wrote:
> 
>>>> But you objected that *this is specific. You claimed that it dispatches in
>>>> f(), if it does then it is of T'Class.
>>
>>> This concept has no meaning there.
>>
>> Which concept?
> 
> T'Class. It has no meaning, because there is no possible family of
> types there. There is one type T and everything dispatches to T.
> Forget dynamic dispatch where you are in the constructor.

Sorry, are you arguing against the concept or against C++ model (that uses
this concept in an inappropriate way? (:-))

>> That is irrelevant. Relevant is whether the object is safe to use as T.
> 
> The funny part is that formally there is no such object yet.

Finally. And how is it called when something non-existing can still be used
as if it were existing and, moreover, were of the type T? It is called
"untyped mess."

>>> This is important - you can *use* the fields, because they were
>>> already constructed.
>>
>> A pile of fields does not make it object. Again, it is either untyped, or
>> else you are talking about another type: struct {...}. You have constructed
>> "struct," a predefined container type, you didn't T.
> 
> Yes. Still, there is a lot I can do from the methods of the class,
> which understand the state. These methods can even operate before the
> invariants of T are fully established. After all, it's their natural
> ability.

No, this is incorrect. Methods are allowed to break public invariants in
the course of their execution. But that by no means implies that invariants
do not hold on the method's input and output.

> This is also why it's even more important *not* to dispatch into
> vacuum.

You have a wrong impression that I wanted this. That's wrong. My position
is even stricter. It is that it is inconsistent to dispatch even after
having a fully constructed T. To dispatch you have to construct a
polymorphic object (like T'Class), to have a specific T is only a premise
of construction of T'Class. (There could be special types (like tagged
types in Ada and "class" types in C++) for which construction of a
polymorphic object may happen automatically. That does not mean, that it is
the same object.)

Note the implication: if you construct an S derived from T and have
constructed only T, then T'Class is still not constructed and you cannot
dispatch. Only after construction of S, you can proceed to T'Class, and
after that, lo and behold, you can dispatch safely!

This is why I am arguing for class-wide constructors (strictly speaking,
user-defined hooks on the completion of). From there you could dispatch.

>>> The inconsistency can be found
>>> under the assumption that types must be invariant.
>>
>> Types aren't invariant. You are free to construct new types.
> 
> That's what I'm writing about. What are we discussing, then? :-)

That the type of a given object is invariant.

=> you can't create an object before its type
=> you can't destroy a type before any of its objects
=> you can't change the type of an object

When you pass an in out object as a parameter of different (but compatible)
type, the model tells that first a new object is created. Then that is
passed to the subprogram. After completion the result is converted back.
This is the semantic level. Whether the implementation would reuse the
memory allocated for the original object is irrelevant.

>> If you
>> constrain the mutation scale in *any* way, and tell the client about that,
>> then this would *automatically* define the true type of the object, and all
>> these fake types you are talking about would be just aberrations.
> 
> That's true. How do you compare C++ with Ada in this context?

In Ada it is called T'Class.

> I understand your objection that in this case (given the above
> constraint) it's not T anymore, but making it formally complete would
> bring more practical issues than it theoretically solves.

That is another discussion. The point was about inconsistency of the model.
How to repair it in C++ without separation of T and T'Class? I don't know.
I strongly believe that there is no way. IMO the issue is tightly related
to the naive set theory. It does not hold.

> And again, Ada doesn't solve it, either.

Ada does not have user-defined constructors, so it stays consistent. Though
this greatly limits Ada and gives the birth to chimerical constructs like
functions returning limited objects.

>>> I don't get it. You can call & on the type, unless operator& is
>>> private.
>>
>> Which type? You said that the only thing objects have for sure is their
>> names. Period. No types, no operations.
> 
> No, I didn't say that. I said that the address (name) persists while
> the object moves along the constructor chain. When it comes to
> operations, note that the object's type move along the known type
> chain. This also allows to predict which operations are available. In
> particular (in the case of constructor chain), operations never
> disappear, so if you already know some of them at any given stage, you
> can keep using them.

Formally it would mean that the object is of the type

    class {T1,T2,...,Tn}

where T1-Tn is the construction path. This type has the operation
"address," etc. Alas, this is not what actually happens. Because, as you
claimed, you can dispatch and the target of dispatch moves => it is more
than just one class. It is not even a type, it is a set of. Something like

   { class {T1}, class {T1, T2}, class {T1, T2, T3}, ... }

But the effect of all this horrible mess is equivalent to the following
simpler set of types:

   { T1, T2, T3, ... }

This is why I initially said that

1. it does not dispatch (in the simpler model);
2. the type is specific in each "constructor";
3. the thingy is untyped because we have a set of types instead one

>>> So - what about growing up?
>>
>> The attribute Age.
> 
> You try to hide now. :-)
> Again: Human has two subclasses: Child and Adult.
> 
> What about growing up?

The attributes do. If you want to keep it one class (human), you are forced
to have a model where each client would be able to deal with either a child
or an adult. So growing up humans is all about different attributes
(object's values) of humans. When, you a client see a human and what to
classify him/her, you ask that human about the age, make a medical
examination etc. There is no other way.

>>> The only way to grow up the child in Ada and C++ is to kill a child
>>> and resurrect it as an adult, reshuffling all necessary references in
>>> the system so that they point to the new entity.
>>> Not funny.
>>
>> But alas real, our cells get replaced as we grow.
> 
> As far as I know, not in heart tissue. But this is off-topic. :-)

Even so, the molecules of cells are permanently replaced. Further the atoms
building them fundamentally cannot be identified. There is no such thing as
identity in the physical world.

>>> No. dynamic_cast gives another view on the same object. It does not
>>> give another object.
>>
>> So you want to introduce views. There are object and views of. Care to
>> define a difference, in terms *types* of course? (:-))
> 
> View is what you have with references to objects. There is nothing to
> introduce here.

You did references. Is reference an object? What is the type of?

> In particular, you can have *many views of different types* on *the
> same object* at *the same time*. Don't try to hide it under the
> carpet, it will pop up anyway. :-)

To hide what? I doubt very much that we could define views in a more or
less acceptable way. Because aliasing cannot be. My point is, a view is
just a new object of a *new* type.

>>> You talk with someone over the phone. At some point you discover that
>>> it's an adult, then you agree with him (dynamic_cast) that from now on
>>> you can talk like adults. It's still the same person, you just know
>>> more about him.
>>
>> Good example. We talk over a communication protocol, which itself is an
>> object. Yes, there will be another protocol instance after handshaking. Of
>> course we could design the protocol level as a parameter. But then,
>> firstly, that would not make it a different type, and secondly, that could
>> be an unsafe design, as we could forget about the constraint and get an
>> exception.
> 
> And your point is? Mine was that dynamic_cast does not give another
> object.

It does. Any function returns a new object. Simple? (:-))

>>> Without this "complexity" you get real complexity with dispatching to
>>> vacuum.
>>
>> Nope, it is crystal clear in vacuum: no class, no dispatch. Type and only
>> type have to tell what's going on.
> 
> I don't get it. If I dispatch to the type where even the fields were
> not yet initialized, how I can decide what I can do? Which type should
> I ask?

q.e.d.

You cannot consistently answer this question => the model is wrong.

>>> It's
>>> after the constructor finishes when the clients can use the object via
>>> its public interface. From their point of view the object exists after
>>> the constructor finished. From the constructor point of view, the
>>> fields exist before the constructor's body is executed. Easy.
>>
>> = you shall not use any public interface methods from the constructor's
>> body => C++ is inconsistent allowing this.
> 
> Yes. My turn: Ada is insonsistent here as well.

Ada is consistent because Initialize is not a constructor. It washes its
hands: if we don't know how to do it properly, we better don't. 

>> ----------------
>> Just annotate types. Fields construction does struct {...}. No less, no
>> more. You can access fields because struct {...} has been constructed. You
>> cannot use anything from T / struct {...}, because T is not yet
>> constructed. (/ denotes set subtraction). The object of struct {...} does
>> exist. The object T does not. Disagree?
> 
> I like it. Really.
> Well, not really. You might leak the 'this' pointer (which would be of
> type struct {...}) out of the constructor and anybody would be able to
> mess with fields, which breaks encapsulation.
>
> Instead of struct {...} I'd prefer:
> 
> class T_fields
> {
> private:
>    ... // all fields (or their references) go here
> 
>    friend class T;
> };
> 
> Private fields make it safe to leak the pointer outside (it's useless
> outside, so no harm in leaking it), and friend declaration allows
> access from T's constructor itself and other involved methods.
> Another, maybe simpler, option would be to make the struct {...}
> private in T, so it could not be even leaked and no protection is then
> necessary.

It does not leak because the fields were exposed (by the programmer), i.e.
semantically T inherits from struct{...}. It was his decision. Surely he
should be able not to publicly inherit from struct, then the fields
wouldn't be publicly accessible.

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



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

* Re: Inherited Methods and such
  2007-09-24  9:32                                         ` Dmitry A. Kazakov
@ 2007-09-24 15:02                                           ` Maciej Sobczak
  2007-09-24 19:20                                             ` Dmitry A. Kazakov
  0 siblings, 1 reply; 74+ messages in thread
From: Maciej Sobczak @ 2007-09-24 15:02 UTC (permalink / raw)


On 24 Wrz, 11:32, "Dmitry A. Kazakov" <mail...@dmitry-kazakov.de>
wrote:

> > T'Class. It has no meaning, because there is no possible family of
> > types there. There is one type T and everything dispatches to T.
> > Forget dynamic dispatch where you are in the constructor.
>
> Sorry, are you arguing against the concept or against C++ model (that uses
> this concept in an inappropriate way? (:-))

I'm arguing against using this concept in the context of constructor.
It does not belong there for the simple reason - T'Class denotes a
potential family of specific types. These don't exist yet.

> > The funny part is that formally there is no such object yet.
>
> Finally. And how is it called when something non-existing can still be used
> as if it were existing and, moreover, were of the type T? It is called
> "untyped mess."

And how is it called when something dispatches into vacuum?
I think "untyped mess" is not exact. Close, but not exact. :-)

> > Still, there is a lot I can do from the methods of the class,
> > which understand the state. These methods can even operate before the
> > invariants of T are fully established. After all, it's their natural
> > ability.
>
> No, this is incorrect. Methods are allowed to break public invariants in
> the course of their execution. But that by no means implies that invariants
> do not hold on the method's input and output.

It's up to the designer to decide which methods can be called and
which invariants should hold and where.

> My position
> is even stricter. It is that it is inconsistent to dispatch even after
> having a fully constructed T. To dispatch you have to construct a
> polymorphic object (like T'Class), to have a specific T is only a premise
> of construction of T'Class.

OK. Let's forbid to call virtual functions altogether until the full
object is ready. I'm fine with such restriction.

> Note the implication: if you construct an S derived from T and have
> constructed only T, then T'Class is still not constructed and you cannot
> dispatch. Only after construction of S, you can proceed to T'Class, and
> after that, lo and behold, you can dispatch safely!

This is extreme, but I understand it.
The C++ way is to allow dispatch with just T, by restricting the
dispatch itself to T only (this is more meaningful when more types are
involved in the chain, so that you can already talk about T'Class
after constructing T *and* S, but not yet U, V and W - this is already
polymorphic in the restricted T+S sense).

> This is why I am arguing for class-wide constructors (strictly speaking,
> user-defined hooks on the completion of). From there you could dispatch.

OK. Such a restriction makes sense when seen in isolation, but makes
some scenarios rather clunky, see below.

> > What are we discussing, then? :-)
>
> That the type of a given object is invariant.

Let's say I want to register my new object from T's constructor in
some map or somewhere. I don't want to hook to complete T'Class,
because I cannot force users deriving from my T to call be back so
that I can register. I want the registration process to be
encapsulated and invisible and I want to do it from T's constructor.
If you restrict the possibility to use T'Class inside the constructor,
you have to way to register T'Class reference/pointer from T's
constructor. Too bad.
One possibility would be to register kind of proxy instead and that
proxy could be used to query whether the object is ready or not. But
this is not really different from having a built-in dispatch that
changes the depth of dispatch dynamically, so we're back at the same
problem.

> > How do you compare C++ with Ada in this context?
>
> In Ada it is called T'Class.

I'm asking about the "aberration" part.

> > I understand your objection that in this case (given the above
> > constraint) it's not T anymore, but making it formally complete would
> > bring more practical issues than it theoretically solves.
>
> That is another discussion. The point was about inconsistency of the model.

Yes, it has holes. I still prefer it due to the fact that it does not
dispatch into vacuum.

> Ada does not have user-defined constructors, so it stays consistent.

Yes.

> Though
> this greatly limits Ada and gives the birth to chimerical constructs like
> functions returning limited objects.

This is just part of the problem. Another part is dispatch into
vacuum.

> Formally it would mean that the object is of the type
>
>     class {T1,T2,...,Tn}
>
> where T1-Tn is the construction path. This type has the operation
> "address," etc. Alas, this is not what actually happens. Because, as you
> claimed, you can dispatch and the target of dispatch moves => it is more
> than just one class. It is not even a type, it is a set of. Something like
>
>    { class {T1}, class {T1, T2}, class {T1, T2, T3}, ... }

Yes. Keep in mind the temporal aspect of this.

> But the effect of all this horrible mess is equivalent to the following
> simpler set of types:
>
>    { T1, T2, T3, ... }

No. You lost the temporal aspect. :-)

> This is why I initially said that
>
> 1. it does not dispatch (in the simpler model);
> 2. the type is specific in each "constructor";
> 3. the thingy is untyped because we have a set of types instead one

Surely you lost the temporal aspect.
The thingy is typed, because at any given moment the target of the
dispatch can be predicted.

> > What about growing up?
>
> The attributes do.

No. Children and Adults have different interfaces. It's not even the
case-block-pretending-dispatch. There are different primitive
operations, some of them not even rooted in Human.

> If you want to keep it one class (human), you are forced
> to have a model where each client would be able to deal with either a child
> or an adult.

No. I can add new operations in derived types.

> > View is what you have with references to objects. There is nothing to
> > introduce here.
>
> You did references. Is reference an object? What is the type of?

Reference is not an object. It it only a "telephone conversation" with
some object.
Whether references need to have types at all is a matter of taste.
They can be transparent (untyped), but in typed language they should
be typed and their type can be T'Class (or just T if the language
allows it - this influences how the dispatch works).

> My point is, a view is
> just a new object of a *new* type.

It does not have to be, but I find it acceptable except that I don't
think you need a *new* type for them. In the extreme case (see Java)
references are the only way to talk to objects, so no new type is
needed, because there is no way to confuse the type of the object with
the type of the reference.

> > And your point is? Mine was that dynamic_cast does not give another
> > object.
>
> It does. Any function returns a new object. Simple? (:-))

If you define it like this, yes. But I can extend the notion of the
function so that function can return views, which are not object.
Simple. :-)

Control question: what is the identity of the view? :-)

> > If I dispatch to the type where even the fields were
> > not yet initialized, how I can decide what I can do? Which type should
> > I ask?
>
> q.e.d.
>
> You cannot consistently answer this question => the model is wrong.

C++ avoids this question by not dispatching into vacuum. If there is
no question, then there is no problem with the answer or the lack of
it.

> Ada is consistent because Initialize is not a constructor. It washes its
> hands: if we don't know how to do it properly, we better don't.

Leaving the problem open? I don't call it consistent.

> >> ----------------
> >> Just annotate types. Fields construction does struct {...}. No less, no
> >> more.
[...]
> > I like it. Really.
> > Well, not really.

Indeed - I cannot register from constructor.
Registering a proxy leaves me again with some form of dynamic_cast
changing its behavior with time, but just with different name.

--
Maciej Sobczak
http://www.msobczak.com/




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

* Re: Inherited Methods and such
  2007-09-24 15:02                                           ` Maciej Sobczak
@ 2007-09-24 19:20                                             ` Dmitry A. Kazakov
  2007-09-25 20:53                                               ` Maciej Sobczak
  0 siblings, 1 reply; 74+ messages in thread
From: Dmitry A. Kazakov @ 2007-09-24 19:20 UTC (permalink / raw)


On Mon, 24 Sep 2007 08:02:05 -0700, Maciej Sobczak wrote:

> On 24 Wrz, 11:32, "Dmitry A. Kazakov" <mail...@dmitry-kazakov.de>
> wrote:
> 
>>> T'Class. It has no meaning, because there is no possible family of
>>> types there. There is one type T and everything dispatches to T.
>>> Forget dynamic dispatch where you are in the constructor.
>>
>> Sorry, are you arguing against the concept or against C++ model (that uses
>> this concept in an inappropriate way? (:-))
> 
> I'm arguing against using this concept in the context of constructor.

Concepts apply to the language as a whole. This includes everything already
written and anything that could be written in the language...

> It does not belong there for the simple reason - T'Class denotes a
> potential family of specific types. These don't exist yet.

Are you making my point? (:-)) How can you dispatch on something
non-existing? If you don't want to dispatch, then just do not! Specific
types do not dispatch.

>>> Still, there is a lot I can do from the methods of the class,
>>> which understand the state. These methods can even operate before the
>>> invariants of T are fully established. After all, it's their natural
>>> ability.
>>
>> No, this is incorrect. Methods are allowed to break public invariants in
>> the course of their execution. But that by no means implies that invariants
>> do not hold on the method's input and output.
> 
> It's up to the designer to decide which methods can be called and
> which invariants should hold and where.

An invariant that doesn't hold isn't invariant! (:-))

>> My position
>> is even stricter. It is that it is inconsistent to dispatch even after
>> having a fully constructed T. To dispatch you have to construct a
>> polymorphic object (like T'Class), to have a specific T is only a premise
>> of construction of T'Class.
> 
> OK. Let's forbid to call virtual functions altogether until the full
> object is ready. I'm fine with such restriction.

No, because:

1. It introduces another type T w/o primitive operations;

2. This degenerate is anyway inviable, as you can call primitive operations
from non-primitive ones. It would leak, or else you should mount one
constraint on another in order to keep the card house from collapse;

3. There already exists a type that has the desired property of
non-dispatching, it is T (specific).

>> Note the implication: if you construct an S derived from T and have
>> constructed only T, then T'Class is still not constructed and you cannot
>> dispatch. Only after construction of S, you can proceed to T'Class, and
>> after that, lo and behold, you can dispatch safely!
> 
> This is extreme, but I understand it.
> The C++ way is to allow dispatch with just T, by restricting the
> dispatch itself to T only (this is more meaningful when more types are
> involved in the chain, so that you can already talk about T'Class
> after constructing T *and* S, but not yet U, V and W - this is already
> polymorphic in the restricted T+S sense).

You cannot do that because it contradicts to the definition of class T
(T'Class). It is no problem to re-define it as class {T,S}. But in the
contract of it was defined as T'Class = class{T, S, anything else derived
from T}. Thus calling any method of the latter on the former breaks the
types contract => untyped.

You are in an unresolvable contradiction by trying to mix class and its
truncations. It is untyped, just because mixing types in untyped. Ada 95
solved that by naming the truncation as T and the class of as T'Class. The
rest was easy.

> Let's say I want to register my new object from T's constructor in
> some map or somewhere. I don't want to hook to complete T'Class,
> because I cannot force users deriving from my T to call be back so
> that I can register. I want the registration process to be
> encapsulated and invisible and I want to do it from T's constructor.
> If you restrict the possibility to use T'Class inside the constructor,
> you have to way to register T'Class reference/pointer from T's
> constructor. Too bad.

I don't understand your example - if you don't derive from T, its
constructor will not be called. What do you want, to register objects of
the type T or objects of the type T'Class? Answer this and you will know
from where to do the registration of what.

>>> How do you compare C++ with Ada in this context?
>>
>> In Ada it is called T'Class.
> 
> I'm asking about the "aberration" part.

Ada has some inconsistencies. For example, bounds-sliding renaming of
arrays.

>>> I understand your objection that in this case (given the above
>>> constraint) it's not T anymore, but making it formally complete would
>>> bring more practical issues than it theoretically solves.
>>
>> That is another discussion. The point was about inconsistency of the model.
> 
> Yes, it has holes. I still prefer it due to the fact that it does not
> dispatch into vacuum.

Oh, yes, let's blow up everything around, then nobody would notice that
hole! (:-))

>> Though
>> this greatly limits Ada and gives the birth to chimerical constructs like
>> functions returning limited objects.
> 
> This is just part of the problem. Another part is dispatch into
> vacuum.

Not in Ada. You cannot dispatch in Initialize because its argument is
specific.

>> But the effect of all this horrible mess is equivalent to the following
>> simpler set of types:
>>
>>    { T1, T2, T3, ... }
> 
> No. You lost the temporal aspect. :-)
> 
>> This is why I initially said that
>>
>> 1. it does not dispatch (in the simpler model);
>> 2. the type is specific in each "constructor";
>> 3. the thingy is untyped because we have a set of types instead one
> 
> Surely you lost the temporal aspect.

How and why should I care?
 
> The thingy is typed, because at any given moment the target of the
> dispatch can be predicted.

Huh, the address of any JMP instruction can be predicted at any given
moment. To be typed you should be able to express your predictions in terms
of types.

>>> What about growing up?
>>
>> The attributes do.
> 
> No. Children and Adults have different interfaces. It's not even the
> case-block-pretending-dispatch. There are different primitive
> operations, some of them not even rooted in Human.

You could say it simpler: Children and Adult are different types. Period.

>> If you want to keep it one class (human), you are forced
>> to have a model where each client would be able to deal with either a child
>> or an adult.
> 
> No. I can add new operations in derived types.

But not to Human. Crocodiles eat humans. The only operation they know is
"chew."

>>> View is what you have with references to objects. There is nothing to
>>> introduce here.
>>
>> You did references. Is reference an object? What is the type of?
> 
> Reference is not an object. It it only a "telephone conversation" with
> some object.

Do "telephone conversations" have value? (:-)) What is the value of?

> Whether references need to have types at all is a matter of taste.

It is a matter of completeness. You have introduced things beyond values,
types and objects. Either your model is fundamentally too weak to describe
references without that or else you lost your way.

"entities should not be multiplied beyond necessity."

>> My point is, a view is just a new object of a *new* type.
> 
> It does not have to be, but I find it acceptable except that I don't
> think you need a *new* type for them.

We need it to prevent infinite recursion. More generally, here the
behavioral principle applies: if there is a difference between object and a
reference to => that should be observable through the behaviors of => they
have different behaviors => they have different types.

>>> And your point is? Mine was that dynamic_cast does not give another
>>> object.
>>
>> It does. Any function returns a new object. Simple? (:-))
> 
> If you define it like this, yes. But I can extend the notion of the
> function so that function can return views, which are not object.
> Simple. :-)

Yup, Pickwickian functions in Ada 2005 (:-))

> Control question: what is the identity of the view? :-)

There is nothing special in identity. A view can have many identities:

1. The name of the view
2. The type of the view
3. The target if the type of is comparable
4. The tag of the view if it is polymorphic
5. The tag of the target if that is polymorphic
...
 
>> Ada is consistent because Initialize is not a constructor. It washes its
>> hands: if we don't know how to do it properly, we better don't.
> 
> Leaving the problem open? I don't call it consistent.

Incomplete, but consistent. Absence of solution is better than a wrong one.

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



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

* Re: Inherited Methods and such
  2007-09-22  8:48                                 ` Dmitry A. Kazakov
  2007-09-22 21:53                                   ` Maciej Sobczak
@ 2007-09-25  1:59                                   ` Randy Brukardt
  2007-09-25  8:59                                     ` Dmitry A. Kazakov
  1 sibling, 1 reply; 74+ messages in thread
From: Randy Brukardt @ 2007-09-25  1:59 UTC (permalink / raw)


"Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> wrote in message
news:9ukf2wtqjs0q$.iuijmal4x56b$.dlg@40tude.net...
...
> > Right. Ada doesn't have *any* constructors. :-)
>
> Surely it has. The point is that in NO typed language a constructor can be
> defined solely in the language terms. Constructors have to be generated
> "per magic." But the language shall provide hooks for user-defined
> insertions in the generated constructors at the points, where the type
> contracts are satisfied. Isn't it obvious?
>
> Ada keeps on trying to use only magic. Returning limited types, new in Ada
> 2005, is just another step in this direction.

Ada has no choice. You agree that "magic" is needed to do the memory
allocation, layout, etc. for the underlying object. (To say otherwise would
be to require all objects to be fixed in size.) But that is not divisible
from the initialization of the value in Ada (and the values of the
components), because the memory allocation depends on the value of the
discriminants, and even the components that exist depends on those values.

One could require all objects to be constrained at birth, or to not have
discriminant-dependent components, but such a language is not Ada (even if
it would work just fine for O-O purposes). Those things are a legacy of
Ada's Ada 83 roots.

Note that this is very similar to the reasons that Ada cannot have "real"
user-defined assignment; it's not possible to divide an assignment into
components managed separately.

I made the mistake of thinking as you did when we implemented discriminated
records in Janus/Ada (a long, long time ago). It led to a never-ending
series of bugs, and an equal amount of work replacing it with code that
actually works (and does all of the steps of initialization at once).

> This is IMO a road to nowhere.

It's way better than no constructors at all, which is the only other option.

> And also, it is inconsistent with Ada's own stance on types
> matching by-name rather than by-structure.

This I don't see at all. Since constructors are named (and I agree that
Initialize is not a constructor in any sense, its more of an initial
mutator) and are tied to the return types, they fit well into the Ada type
model. The best constructors are dispatching, and those surely are
one-to-one with the types that they construct (Ada 95 required those to be
overridden for each new type; the Amendment changed that over my strong
objections, but even so you surely are allowed to override them).

                      Randy.





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

* Re: Inherited Methods and such
  2007-09-25  1:59                                   ` Randy Brukardt
@ 2007-09-25  8:59                                     ` Dmitry A. Kazakov
  2007-09-25 21:02                                       ` Randy Brukardt
  0 siblings, 1 reply; 74+ messages in thread
From: Dmitry A. Kazakov @ 2007-09-25  8:59 UTC (permalink / raw)


On Mon, 24 Sep 2007 20:59:53 -0500, Randy Brukardt wrote:

> "Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> wrote in message
> news:9ukf2wtqjs0q$.iuijmal4x56b$.dlg@40tude.net...
> ...
>>> Right. Ada doesn't have *any* constructors. :-)
>>
>> Surely it has. The point is that in NO typed language a constructor can be
>> defined solely in the language terms. Constructors have to be generated
>> "per magic." But the language shall provide hooks for user-defined
>> insertions in the generated constructors at the points, where the type
>> contracts are satisfied. Isn't it obvious?
>>
>> Ada keeps on trying to use only magic. Returning limited types, new in Ada
>> 2005, is just another step in this direction.
> 
> Ada has no choice. You agree that "magic" is needed to do the memory
> allocation, layout, etc. for the underlying object. (To say otherwise would
> be to require all objects to be fixed in size.) But that is not divisible
> from the initialization of the value in Ada (and the values of the
> components), because the memory allocation depends on the value of the
> discriminants, and even the components that exist depends on those values.

It can be made divisible:

For each type T we define a plain built-in record type T'Constraints such
that it has all the constraints (discriminants, bounds, tags) of T. Prior
allocation of T a user hook is called to return T'Constrants. It gets the
parameters of the constructor. Then the memory is allocated. After that the
implementation type of T is constructed (the constraints are set, the
components are constructed). Then we proceed to the construction of T. Here
the parent types are constructed first and finally a user hook equivalent
to Initialize is called on T. For class-wide types there is an additional
step where the class-wide user hook is called.

> Note that this is very similar to the reasons that Ada cannot have "real"
> user-defined assignment; it's not possible to divide an assignment into
> components managed separately.

Same as above. Moreover it would be probably possible to make the old LHS
visible in the hook computing the constraints.

>> And also, it is inconsistent with Ada's own stance on types
>> matching by-name rather than by-structure.
> 
> This I don't see at all. Since constructors are named (and I agree that
> Initialize is not a constructor in any sense, its more of an initial
> mutator) and are tied to the return types, they fit well into the Ada type
> model. The best constructors are dispatching, and those surely are
> one-to-one with the types that they construct (Ada 95 required those to be
> overridden for each new type; the Amendment changed that over my strong
> objections, but even so you surely are allowed to override them).

That was a mistake, but overriding is a wrong model for constructors
anyway. Constructors are extended but overridden. Further I don't think
that constructing functions can be called constructors.

We don't need constructors, we need insertions in the compiler-generated
ones.

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



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

* Re: Inherited Methods and such
  2007-09-24 19:20                                             ` Dmitry A. Kazakov
@ 2007-09-25 20:53                                               ` Maciej Sobczak
  2007-09-26 10:42                                                 ` Dmitry A. Kazakov
                                                                   ` (2 more replies)
  0 siblings, 3 replies; 74+ messages in thread
From: Maciej Sobczak @ 2007-09-25 20:53 UTC (permalink / raw)


On 24 Wrz, 21:20, "Dmitry A. Kazakov" <mail...@dmitry-kazakov.de>
wrote:

> > OK. Let's forbid to call virtual functions altogether until the full
> > object is ready. I'm fine with such restriction.
>
> No, because:
>
> 1. It introduces another type T w/o primitive operations;

Indeed.

> 2. This degenerate is anyway inviable, as you can call primitive operations
> from non-primitive ones.
>
> 3. There already exists a type that has the desired property of
> non-dispatching, it is T (specific).

2. and 3. are inconsistent.


> You are in an unresolvable contradiction by trying to mix class and its
> truncations. It is untyped, just because mixing types in untyped. Ada 95
> solved that by naming the truncation as T and the class of as T'Class. The
> rest was easy.

Yes. Like here:

-- p.ads:
with Ada.Finalization;
package P is

   type T is new Ada.Finalization.Controlled with null record;

   overriding procedure Initialize (X : in out T);
   procedure Some_Operation (X : in T);

   type R is range 10 .. 20;

   type S is new T with record
      I : R;
   end record;

   overriding procedure Initialize (X : in out S);
   overriding procedure Some_Operation (X : in S);

end P;

-- p.adb:
with Ada.Text_IO;
use Ada.Text_IO;

package body P is

   procedure Dispatch (X : in T'Class) is
   begin
      Put ("Calling Some_Operation with T'Class");
      New_Line (2);
      Some_Operation (X);
   end Dispatch;

   procedure Initialize (X : in out T) is
   begin
      Put ("P.Initialize for T");
      New_Line;
      Put ("-> doing some operation with the object");
      New_Line (2);
      Dispatch (X);
   end Initialize;

   procedure Some_Operation (X : in T) is
   begin
      Put ("P.Some_Operation for T");
      New_Line (2);
   end Some_Operation;

   procedure Initialize (X : in out S) is
   begin
      Put ("P.Initialize for S, but first let's initialize T" &
           " and then we will take care of our (S) components");
      New_Line (2);
      Initialize (T (X));
      Put ("OK, finished with the supertype (T)");
      New_Line;
      Put ("-> let's initialize our own (S) components...");
      New_Line;
      X.I := 15;
      Put ("-> components initialized and now I =" & R'Image (X.I));
      New_Line (2);
   end Initialize;

   procedure Some_Operation (X : in S) is
   begin
      Put ("P.Some_Operation for S");
      New_Line;
      Put ("-> here we are SURE that all components are correct");
      New_Line;
      Put ("-> for example I =" & R'Image (X.I));
      Put (" which is SURELY in the range " &
           R'Image (R'First) & " .." & R'Image (R'Last));
      New_Line;
      Put ("-> otherwise it would be UNTYPED MESS");
      New_Line (2);
   end Some_Operation;

end P;

-- a.adb:
with P;
procedure A is
   X : P.S;
begin
   null;
end;

$ gnatmake a
gcc -c a.adb
gcc -c p.adb
gnatbind -x a.ali
gnatlink a.ali
$ ./a
P.Initialize for S, but first let's initialize T and then we will take
care of our (S) components

P.Initialize for T
-> doing some operation with the object

Calling Some_Operation with T'Class

P.Some_Operation for S
-> here we are SURE that all components are correct
-> for example I = 0 which is SURELY in the range  10 .. 20
-> otherwise it would be UNTYPED MESS

OK, finished with the supertype (T)
-> let's initialize our own (S) components...
-> components initialized and now I = 15

$

The above program prints 0 as the value of an object which type is
range 10 .. 20.

What's even more funny, I can add the following inside Some_Operation
for S:

declare
   J : R := X.I;  -- at this point X.I = 0
begin
   Put (R'Image (J));
end;

Guess what will happen. No constraint_error and 0 is assigned to J.
Why? Because the poor compiler assumed that if X.I is of type R, then
no checks are necessary.

Wow.

> > Let's say I want to register my new object from T's constructor in
> > some map or somewhere. I don't want to hook to complete T'Class,
> > because I cannot force users deriving from my T to call be back so
> > that I can register. I want the registration process to be
> > encapsulated and invisible and I want to do it from T's constructor.
> > If you restrict the possibility to use T'Class inside the constructor,
> > you have to way to register T'Class reference/pointer from T's
> > constructor. Too bad.
>
> I don't understand your example - if you don't derive from T, its
> constructor will not be called. What do you want, to register objects of
> the type T or objects of the type T'Class? Answer this and you will know
> from where to do the registration of what.

I want to register T'Class, but I don't want to involve those who
derive from T.


> > Another part is dispatch into
> > vacuum.
>
> Not in Ada. You cannot dispatch in Initialize because its argument is
> specific.

See above example.

> >>> What about growing up?
>
> >> The attributes do.
>
> > No. Children and Adults have different interfaces. It's not even the
> > case-block-pretending-dispatch. There are different primitive
> > operations, some of them not even rooted in Human.
>
> You could say it simpler: Children and Adult are different types. Period.

They are both in Human'Class. You try to escape the problem instead of
solving it.


> >> My point is, a view is just a new object of a *new* type.
>
> > It does not have to be, but I find it acceptable except that I don't
> > think you need a *new* type for them.
>
> We need it to prevent infinite recursion. More generally, here the
> behavioral principle applies: if there is a difference between object and a
> reference to => that should be observable through the behaviors of => they
> have different behaviors => they have different types.

What if they don't have different behaviors?
There are two ways to achieve it:
1. Prevent anybody from interacting with the object directly, so there
is nothing to compare (Java).
2. Make references just names, which are syntax entities (C++, in some
contexts).


> > Control question: what is the identity of the view? :-)
>
> There is nothing special in identity. A view can have many identities:
>
> 1. The name of the view
> 2. The type of the view
> 3. The target if the type of is comparable
> 4. The tag of the view if it is polymorphic
> 5. The tag of the target if that is polymorphic
> ...

6. The identity of the target.

> >> Ada is consistent because Initialize is not a constructor. It washes its
> >> hands: if we don't know how to do it properly, we better don't.
>
> > Leaving the problem open? I don't call it consistent.
>
> Incomplete, but consistent. Absence of solution is better than a wrong one.

A wrong solution is exploited in the example above.
This is *really* *untyped* *mess* (tm).

Let's also not forget that Initialize is useless as a constructor (and
thus incomparable to C++) anyway due to the lack of parameters. But so-
called "constructor functions" will not solve this problem.

--
Maciej Sobczak
http://www.msobczak.com/




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

* Re: Inherited Methods and such
  2007-09-25  8:59                                     ` Dmitry A. Kazakov
@ 2007-09-25 21:02                                       ` Randy Brukardt
  2007-09-26 12:42                                         ` Dmitry A. Kazakov
  0 siblings, 1 reply; 74+ messages in thread
From: Randy Brukardt @ 2007-09-25 21:02 UTC (permalink / raw)


"Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> wrote in message
news:19ie8rfpiitdx$.i2sz3r6uj66w.dlg@40tude.net...
> On Mon, 24 Sep 2007 20:59:53 -0500, Randy Brukardt wrote:
>
...
> > Ada has no choice. You agree that "magic" is needed to do the memory
> > allocation, layout, etc. for the underlying object. (To say otherwise
would
> > be to require all objects to be fixed in size.) But that is not
divisible
> > from the initialization of the value in Ada (and the values of the
> > components), because the memory allocation depends on the value of the
> > discriminants, and even the components that exist depends on those
values.
>
> It can be made divisible:
>
> For each type T we define a plain built-in record type T'Constraints such
> that it has all the constraints (discriminants, bounds, tags) of T. Prior
> allocation of T a user hook is called to return T'Constrants. It gets the
> parameters of the constructor. Then the memory is allocated. After that
the
> implementation type of T is constructed (the constraints are set, the
> components are constructed). Then we proceed to the construction of T.
Here
> the parent types are constructed first and finally a user hook equivalent
> to Initialize is called on T. For class-wide types there is an additional
> step where the class-wide user hook is called.

That's what I tried to do with Janus/Ada, and it doesn't work in all cases.
I don't have a specific example to give, but the problem comes from the
required order of evaluation; we were reading uninitialized values in
obscure cases. I couldn't find anyway around those requirements.

> > Note that this is very similar to the reasons that Ada cannot have
"real"
> > user-defined assignment; it's not possible to divide an assignment into
> > components managed separately.
>
> Same as above. Moreover it would be probably possible to make the old LHS
> visible in the hook computing the constraints.

No, you can't have visibility on both objects in a hook routine, because
there are cases where one side or the other does not exist. You have to
define three sets of hooks, which would greatly increase the chance of
errors and probably would be too complex for most to understand.

The problem doesn't exist if your language doesn't have disappearing
components, and I would be much more likely to try to approach the problem
from there. But that wouldn't be Ada.

                       Randy.





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

* Re: Inherited Methods and such
  2007-09-25 20:53                                               ` Maciej Sobczak
@ 2007-09-26 10:42                                                 ` Dmitry A. Kazakov
  2007-09-26 21:31                                                   ` Maciej Sobczak
  2007-09-26 12:21                                                 ` Robert A Duff
  2007-09-26 12:26                                                 ` Robert A Duff
  2 siblings, 1 reply; 74+ messages in thread
From: Dmitry A. Kazakov @ 2007-09-26 10:42 UTC (permalink / raw)


On Tue, 25 Sep 2007 13:53:51 -0700, Maciej Sobczak wrote:

> On 24 Wrz, 21:20, "Dmitry A. Kazakov" <mail...@dmitry-kazakov.de>
> wrote:
> 
>> 2. This degenerate is anyway inviable, as you can call primitive operations
>> from non-primitive ones.
>>
>> 3. There already exists a type that has the desired property of
>> non-dispatching, it is T (specific).
> 
> 2. and 3. are inconsistent.

No, because the specific type does not kill primitive operations it only
freezes them. Each primitive operation has a body for a [non-abstract]
specific T. Each primitive operation has a body for T'Class. These bodies
are different. The latter has a dispatching switch that selects to one of
the former bodies. This is BTW another reason why T and T'Class are
necessarily different types.

>> You are in an unresolvable contradiction by trying to mix class and its
>> truncations. It is untyped, just because mixing types in untyped. Ada 95
>> solved that by naming the truncation as T and the class of as T'Class. The
>> rest was easy.
> 
> Yes. Like here:
> 
[...]
>    procedure Initialize (X : in out T) is
>    begin
>       Put ("P.Initialize for T");
>       New_Line;
>       Put ("-> doing some operation with the object");
>       New_Line (2);
>       Dispatch (X);
       ^^^^^^^^^
The problem is here. Initialize converts the object X to T'Class. The
result of this conversion would be wrong if Initialize were a hook on the
constructor of T. But it isn't. So formally everything is OK. Initialize is
a hook on the constructor of T'Class which is officially constructed, no
matter that S's Initialize wasn't called. Your expectations about
Initialize were wrong (the problem is that an average programmers would
likely have similar expectations. So, train people ... rather a C++
approach, but anyway.) 

(Separation of T and T'Class is only the first necessary step. It would not
automatically make "Ada with constructors" consistent.)

Note that the case you presented is just a variant of the more general
problem called "re-dispatch." As your example nicely illustrates,
re-dispatch is inherently inconsistent. This was my point here in c.l.a.
and in comp.object for years.

C++ openly uses re-dispatch, Ada does it through the backdoors like above.
That has to be fixed.

>>> Let's say I want to register my new object from T's constructor in
>>> some map or somewhere. I don't want to hook to complete T'Class,
>>> because I cannot force users deriving from my T to call be back so
>>> that I can register. I want the registration process to be
>>> encapsulated and invisible and I want to do it from T's constructor.
>>> If you restrict the possibility to use T'Class inside the constructor,
>>> you have to way to register T'Class reference/pointer from T's
>>> constructor. Too bad.
>>
>> I don't understand your example - if you don't derive from T, its
>> constructor will not be called. What do you want, to register objects of
>> the type T or objects of the type T'Class? Answer this and you will know
>> from where to do the registration of what.
> 
> I want to register T'Class, but I don't want to involve those who
> derive from T.

Sorry, but T'Class includes them, your design is inconsistent with that.
Can you explain where could it be useful? I still doe not understand the
setting. Do you have method disallowing in mind?

>> You could say it simpler: Children and Adult are different types. Period.
> 
> They are both in Human'Class. You try to escape the problem instead of
> solving it.

I don't see any. If they are in Human'Class <=> they have the behavior of.
A client would use that behavior. Why do you need to change anything?

>>>> My point is, a view is just a new object of a *new* type.
>>
>>> It does not have to be, but I find it acceptable except that I don't
>>> think you need a *new* type for them.
>>
>> We need it to prevent infinite recursion. More generally, here the
>> behavioral principle applies: if there is a difference between object and a
>> reference to => that should be observable through the behaviors of => they
>> have different behaviors => they have different types.
> 
> What if they don't have different behaviors?

What for have you introduced "views," then? (:-))

>>> Control question: what is the identity of the view? :-)
>>
>> There is nothing special in identity. A view can have many identities:
>>
>> 1. The name of the view
>> 2. The type of the view
>> 3. The target if the type of is comparable
>> 4. The tag of the view if it is polymorphic
>> 5. The tag of the target if that is polymorphic
>> ...
> 
> 6. The identity of the target.

Only if that has the identity you are looking for. The target can have or
miss many identities.

>>>> Ada is consistent because Initialize is not a constructor. It washes its
>>>> hands: if we don't know how to do it properly, we better don't.
>>
>>> Leaving the problem open? I don't call it consistent.
>>
>> Incomplete, but consistent. Absence of solution is better than a wrong one.
> 
> A wrong solution is exploited in the example above.

You have switched the subject from the language to its [ab]use. That Ada
does not solve the construction problem is clear to me. But that is not an
inconsistency, it is an incompleteness.

> But so-called "constructor functions" will not solve this problem.

Certainly. I think we, two! (:-)) agree that Ada should have a better
construction model with user insertions. The next step would be to convince
others, and to learn for the mistakes made by C++.

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



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

* Re: Inherited Methods and such
  2007-09-25 20:53                                               ` Maciej Sobczak
  2007-09-26 10:42                                                 ` Dmitry A. Kazakov
@ 2007-09-26 12:21                                                 ` Robert A Duff
  2007-09-26 12:54                                                   ` Dmitry A. Kazakov
  2007-09-26 21:37                                                   ` Maciej Sobczak
  2007-09-26 12:26                                                 ` Robert A Duff
  2 siblings, 2 replies; 74+ messages in thread
From: Robert A Duff @ 2007-09-26 12:21 UTC (permalink / raw)


Maciej Sobczak <see.my.homepage@gmail.com> writes:

> The above program prints 0 as the value of an object which type is
> range 10 .. 20.

Well, it happens to print 0 in one implementation.
According to the RM, this is a bounded error, and
it could just as well raise an exception (or print
some other bogus value).

Is this something fundamentally broken about constructors,
or is just another case of uninitialized variables?
I mean, there are lots of other ways to create uninit vars
in Ada...

> What's even more funny, I can add the following inside Some_Operation
> for S:
>
> declare
>    J : R := X.I;  -- at this point X.I = 0
> begin
>    Put (R'Image (J));
> end;
> 
> Guess what will happen. No constraint_error and 0 is assigned to J.
             ^^^^ might

> Why? Because the poor compiler assumed that if X.I is of type R, then
> no checks are necessary.

Right.  Or it might raise an exception.  I don't find this "more funny"
-- it's equally funny as the previous case of printing 0.

- Bob



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

* Re: Inherited Methods and such
  2007-09-25 20:53                                               ` Maciej Sobczak
  2007-09-26 10:42                                                 ` Dmitry A. Kazakov
  2007-09-26 12:21                                                 ` Robert A Duff
@ 2007-09-26 12:26                                                 ` Robert A Duff
  2007-09-26 21:50                                                   ` Maciej Sobczak
  2 siblings, 1 reply; 74+ messages in thread
From: Robert A Duff @ 2007-09-26 12:26 UTC (permalink / raw)


Maciej Sobczak <see.my.homepage@gmail.com> writes:

> Let's also not forget that Initialize is useless as a constructor (and
> thus incomparable to C++) anyway due to the lack of parameters. But so-
> called "constructor functions" will not solve this problem.

I've lost track of this discussion (sorry -- it's gotten rather
philosophical at times!).  Could you please explain exactly what this
constructor problem is?  And why "constructor functions" do not solve
it?

Are there other languages (c++, perhaps?) that have a complete solution
to this problem?

Thanks.

- Bob



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

* Re: Inherited Methods and such
  2007-09-25 21:02                                       ` Randy Brukardt
@ 2007-09-26 12:42                                         ` Dmitry A. Kazakov
  0 siblings, 0 replies; 74+ messages in thread
From: Dmitry A. Kazakov @ 2007-09-26 12:42 UTC (permalink / raw)


On Tue, 25 Sep 2007 16:02:55 -0500, Randy Brukardt wrote:

> "Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> wrote in message
> news:19ie8rfpiitdx$.i2sz3r6uj66w.dlg@40tude.net...

> That's what I tried to do with Janus/Ada, and it doesn't work in all cases.

Probably one should give another try and consider the cases where it
appears not working. Maybe they indicate language design problems to fix.

BTW, back in the time I wrote compilers, I noticed that the number of cases
tend to geometrically explode. At some point it was impossible to keep
everything in the head. I played with an idea of some metalanguage to
describe all the mess and to generate a part the compiler dealing with
semantic analysis and intermediate code from a [semi-]formal description.
Something equivalent to what grammars do for the syntax analysis. The
problem is that there existed no formal framework comparable with formal
grammars to describe the types system. (UML is a joke, IMO). I am quite
outdated with compiler design, maybe somebody already did that thing.

>>> Note that this is very similar to the reasons that Ada cannot have "real"
>>> user-defined assignment; it's not possible to divide an assignment into
>>> components managed separately.
>>
>> Same as above. Moreover it would be probably possible to make the old LHS
>> visible in the hook computing the constraints.
> 
> No, you can't have visibility on both objects in a hook routine, because
> there are cases where one side or the other does not exist. You have to
> define three sets of hooks, which would greatly increase the chance of
> errors and probably would be too complex for most to understand.

But it is necessary to have three different hooks when dealing with a
class-wide object of an unconstrained type. Maybe even more, because of the
rules about composition of the constraints. There are three independent
hierarchies involved:

1. A hierarchy of constraints (discriminants added/overridden/removed)
2. A hierarchy of components (record extensions)
3. A hierarchy of inheritance (class)

> The problem doesn't exist if your language doesn't have disappearing
> components, and I would be much more likely to try to approach the problem
> from there. But that wouldn't be Ada.

I am not so sure. IMO, the complexity comes from the hierarchies above.
Whether within one of them (the hierarchy of components) items get added or
removed is a detail.

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



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

* Re: Inherited Methods and such
  2007-09-26 12:21                                                 ` Robert A Duff
@ 2007-09-26 12:54                                                   ` Dmitry A. Kazakov
  2007-09-26 21:37                                                   ` Maciej Sobczak
  1 sibling, 0 replies; 74+ messages in thread
From: Dmitry A. Kazakov @ 2007-09-26 12:54 UTC (permalink / raw)


On Wed, 26 Sep 2007 08:21:45 -0400, Robert A Duff wrote:

> Is this something fundamentally broken about constructors,
> or is just another case of uninitialized variables?

Let me throw this in - constructors are basically about statically provable
prevention of un- and mis-initialized things.

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



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

* Re: Inherited Methods and such
  2007-09-26 10:42                                                 ` Dmitry A. Kazakov
@ 2007-09-26 21:31                                                   ` Maciej Sobczak
  2007-09-27 15:02                                                     ` Dmitry A. Kazakov
  0 siblings, 1 reply; 74+ messages in thread
From: Maciej Sobczak @ 2007-09-26 21:31 UTC (permalink / raw)


On 26 Wrz, 12:42, "Dmitry A. Kazakov" <mail...@dmitry-kazakov.de>
wrote:

> >> 2. This degenerate is anyway inviable, as you can call primitive operations
> >> from non-primitive ones.
>
> >> 3. There already exists a type that has the desired property of
> >> non-dispatching, it is T (specific).
>
> > 2. and 3. are inconsistent.
>
> No, because the specific type does not kill primitive operations it only
> freezes them.

Not much. I can recover T'Class from T, as in the example from
previous post.

[...]
> >    procedure Initialize (X : in out T) is
> >    begin
> >       Put ("P.Initialize for T");
> >       New_Line;
> >       Put ("-> doing some operation with the object");
> >       New_Line (2);
> >       Dispatch (X);
>
>        ^^^^^^^^^
> The problem is here. Initialize converts the object X to T'Class.

Bingo. Now you might want to explain to me how is this possible if
type of X is specific T.
How is it possible to recover T'Class from T?
Apparently the type I see in Initialize is somehow decoupled from the
type of the object itself. In other words, what I see in Initialize is
not the whole "truth" about the object. The truth is within the object
itself, not in the parameter of Initialize, and with appropriate
question I can get the full answer.
But this only means that T does not really fully determine X.

With appropriate application of the notion of view this is easy - as
far as I'm concerned, the object is T'Class, but Initialize sees only
the T view on it.

But you claimed that views are not needed. Try to explain this without
reinventing views. :-)

> The
> result of this conversion would be wrong if Initialize were a hook on the
> constructor of T. But it isn't. So formally everything is OK. Initialize is
> a hook on the constructor of T'Class which is officially constructed, no
> matter that S's Initialize wasn't called.

None of Initializes was called before T'Class was complete. That's the
problem. It's entirely reversed, leading to untyped mess.

> Note that the case you presented is just a variant of the more general
> problem called "re-dispatch." As your example nicely illustrates,
> re-dispatch is inherently inconsistent.

I'm not sure whether this is inherent, or just a consequence of the
fact that T'Class is formally complete before it's initialized.

> C++ openly uses re-dispatch, Ada does it through the backdoors like above.

Note that C++ protects the programmer from *this* type of mess.


> > I want to register T'Class, but I don't want to involve those who
> > derive from T.
>
> Sorry, but T'Class includes them, your design is inconsistent with that.
> Can you explain where could it be useful?

Abstract factories perhaps?
I want to set up a framework for the abstract factory design pattern.
The base types and registry are part of the framework and the user
provides the concrete factories. The base types register the factories
in the registry so that later the user can request them and get
concrete behaviour.
This is cool and clean only if the registry mechanics is encapsulated
within the framework, so that the user does not have to care about it
in his own constructors - but this is possible only if the base type
can register T'Class, not just T.

But as we've seen, I can recover T'Class from T, so I can do this
anyway. :-)

> >> You could say it simpler: Children and Adult are different types. Period.
>
> > They are both in Human'Class. You try to escape the problem instead of
> > solving it.
>
> I don't see any. If they are in Human'Class <=> they have the behavior of.
> A client would use that behavior. Why do you need to change anything?

A client can ask for the additional interface after discovering the
concrete type.


> > A wrong solution is exploited in the example above.
>
> You have switched the subject from the language to its [ab]use. That Ada
> does not solve the construction problem is clear to me. But that is not an
> inconsistency, it is an incompleteness.

OK. Name it as you want. I don't like it anyway, no matter what's the
name. :-)

> > But so-called "constructor functions" will not solve this problem.
>
> Certainly. I think we, two! (:-)) agree that Ada should have a better
> construction model with user insertions. The next step would be to convince
> others, and to learn for the mistakes made by C++.

Yes. So what are those mistakes actually? :-)

I don't claim there are none.

--
Maciej Sobczak
http://www.msobczak.com/




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

* Re: Inherited Methods and such
  2007-09-26 12:21                                                 ` Robert A Duff
  2007-09-26 12:54                                                   ` Dmitry A. Kazakov
@ 2007-09-26 21:37                                                   ` Maciej Sobczak
  2007-09-26 23:47                                                     ` Randy Brukardt
  1 sibling, 1 reply; 74+ messages in thread
From: Maciej Sobczak @ 2007-09-26 21:37 UTC (permalink / raw)


On 26 Wrz, 14:21, Robert A Duff <bobd...@shell01.TheWorld.com> wrote:

> Is this something fundamentally broken about constructors,
> or is just another case of uninitialized variables?
> I mean, there are lots of other ways to create uninit vars
> in Ada...

Yes, but for most of them it is easy to analyze the code and spot the
problem.
Most of them can be even found by the compiler itself, without
involving any human-driven code review.

Here the problem is in the interaction between base and derived type.
The important feature of OO is that implementations (bodies) for T and
S might not be available for review at the same time. If you look at T
in isolation, it's clean. If you look at S in isolation (and this is
the only thing you can do if you have only the interface ot T without
the implementation), it's clean as well.
Somehow together they don't work.

The problem with this example is that in general it's impossible to
analyze.

--
Maciej Sobczak
http://www.msobczak.com/




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

* Re: Inherited Methods and such
  2007-09-26 12:26                                                 ` Robert A Duff
@ 2007-09-26 21:50                                                   ` Maciej Sobczak
  2007-09-26 22:20                                                     ` Ray Blaak
                                                                       ` (3 more replies)
  0 siblings, 4 replies; 74+ messages in thread
From: Maciej Sobczak @ 2007-09-26 21:50 UTC (permalink / raw)


On 26 Wrz, 14:26, Robert A Duff <bobd...@shell01.TheWorld.com> wrote:

> > Let's also not forget that Initialize is useless as a constructor (and
> > thus incomparable to C++) anyway due to the lack of parameters. But so-
> > called "constructor functions" will not solve this problem.
>
> I've lost track of this discussion (sorry -- it's gotten rather
> philosophical at times!).

Yes, but I don't feel guilty. ;-)

> Could you please explain exactly what this
> constructor problem is?

The constructor, as known from many other languages, can have
parameters that can possibly influence its work. Ada does not provide
anything built-in for this.

> And why "constructor functions" do not solve
> it?

Constructor functions in Ada are functions that return new objects
(most useful for limited types since Ada 2005) can have parameters,
but they still don't acknowledge the "progressive" nature of the whole
process. This means that no matter whether we use Initialize or
constructor functions, the final type of the object is established at
the very beginning and is kept during the whole construction process.
If this process involves some activities on various levels of the type
inheritance chain, the ugly effects like the one from my example can
result, because primitive operations can be called bypassing the
intended order of component construction.

> Are there other languages (c++, perhaps?) that have a complete solution
> to this problem?

My claim in this lengthy and philosophical discussion is that C++
provides a complete solution which can be shortly described like this:
the final type is not established from the very beginning, but is
progressively moving along the inheritance chain as the subsequent
constructors of base types complete their work. Thank's to this,
primitive operations never dispatch further than what can be
statically reasoned about.

Implementation-wise, this can be done with "just" changing the tag of
the object as each base constructor completes.

--
Maciej Sobczak
http://www.msobczak.com/




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

* Re: Inherited Methods and such
  2007-09-26 21:50                                                   ` Maciej Sobczak
@ 2007-09-26 22:20                                                     ` Ray Blaak
  2007-09-27  0:01                                                     ` Randy Brukardt
                                                                       ` (2 subsequent siblings)
  3 siblings, 0 replies; 74+ messages in thread
From: Ray Blaak @ 2007-09-26 22:20 UTC (permalink / raw)


Maciej Sobczak <see.my.homepage@gmail.com> writes:
> My claim in this lengthy and philosophical discussion is that C++
> provides a complete solution which can be shortly described like this:
> the final type is not established from the very beginning, but is
> progressively moving along the inheritance chain as the subsequent
> constructors of base types complete their work. Thank's to this,
> primitive operations never dispatch further than what can be
> statically reasoned about.

Java has this problem. The rule is to not call overridden functions in the
constructor, since they tend to assume the object is completely constructed.

It is not the type that is the problem. It is calling subclassed methods on an
instance that is not completely constructed yet.

You don't want to progressively change the type either, since that can also
lead to unexpected changes in behaviour.

-- 
Cheers,                                        The Rhythm is around me,
                                               The Rhythm has control.
Ray Blaak                                      The Rhythm is inside me,
rAYblaaK@STRIPCAPStelus.net                    The Rhythm has my soul.



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

* Re: Inherited Methods and such
  2007-09-26 21:37                                                   ` Maciej Sobczak
@ 2007-09-26 23:47                                                     ` Randy Brukardt
  2007-09-27 21:08                                                       ` Maciej Sobczak
  0 siblings, 1 reply; 74+ messages in thread
From: Randy Brukardt @ 2007-09-26 23:47 UTC (permalink / raw)


"Maciej Sobczak" <see.my.homepage@gmail.com> wrote in message
news:1190842629.099822.65770@g4g2000hsf.googlegroups.com...
> On 26 Wrz, 14:21, Robert A Duff <bobd...@shell01.TheWorld.com> wrote:
>
> > Is this something fundamentally broken about constructors,
> > or is just another case of uninitialized variables?
> > I mean, there are lots of other ways to create uninit vars
> > in Ada...
>
> Yes, but for most of them it is easy to analyze the code and spot the
> problem.
> Most of them can be even found by the compiler itself, without
> involving any human-driven code review.
>
> Here the problem is in the interaction between base and derived type.
> The important feature of OO is that implementations (bodies) for T and
> S might not be available for review at the same time. If you look at T
> in isolation, it's clean. If you look at S in isolation (and this is
> the only thing you can do if you have only the interface ot T without
> the implementation), it's clean as well.
> Somehow together they don't work.

No, the problem is quite obviously with T. (Re)-dispatching within
Initialize is a sure-fire way to have a disaster. (A recent discussion on
the ARG list showed this again with dispatching calls in an extended return
statement.) OTOH, any program with such code in it is wrong. It's
unfortunate that there isn't a way for the language to disallow such
dispatching (as Initialize is an ordinary subprogram, after all). The same
is true inside of extended return statements. I wouldn't go as far as Dmitry
and disallow all redispatching, but it surely should not happen until the
object is fully constructed. (I suspect the same is true while it is being
finalized, but whether there is a real problem is not as obvious.)

                                   Randy.





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

* Re: Inherited Methods and such
  2007-09-26 21:50                                                   ` Maciej Sobczak
  2007-09-26 22:20                                                     ` Ray Blaak
@ 2007-09-27  0:01                                                     ` Randy Brukardt
  2007-09-27 13:39                                                     ` Robert A Duff
  2007-09-28 19:02                                                     ` Robert A Duff
  3 siblings, 0 replies; 74+ messages in thread
From: Randy Brukardt @ 2007-09-27  0:01 UTC (permalink / raw)


"Maciej Sobczak" <see.my.homepage@gmail.com> wrote in message
news:1190843408.713838.128690@g4g2000hsf.googlegroups.com...
...
> > And why "constructor functions" do not solve it?
>
> Constructor functions in Ada are functions that return new objects
> (most useful for limited types since Ada 2005) can have parameters,
> but they still don't acknowledge the "progressive" nature of the whole
> process. This means that no matter whether we use Initialize or
> constructor functions, the final type of the object is established at
> the very beginning and is kept during the whole construction process.
> If this process involves some activities on various levels of the type
> inheritance chain, the ugly effects like the one from my example can
> result, because primitive operations can be called bypassing the
> intended order of component construction.

Well, if you insist on doing bad things in your constructor (calling
dispatching routines), you will obviously get problems. So what? I'm not
sure what your argument is.

It's surely not possible for a language to catch all possible errors (simply
because not everything can be proven), so there are always going to be
things that as a programmer we're going to have to avoid. That's part of the
reason why programming is not something everyone can do.

There is no possible mechanism in Ada that could prevent calling of
dispatching routines in bad places. Changing tags cause a number of bizarre
anomalies (and also would probably destroy the existing Ada implementations,
all of which assume that tags never change). Additional "hooks" would help
nothing, because you still could do the bad thing (calling dispatching
routines inside the hook).

So it seems this is a discussion of limited relevance to Ada. Following any
one of a number of good Ada practices would have avoided the problem: Don't
redispatch in a constructor (or Initialize) routine; don't leave scalar
components/objects uninitialized at declaration; never assume a scalar
object is initialized. Maybe this isn't ideal, but it is what we've got.

                            Randy.





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

* Re: Inherited Methods and such
  2007-09-26 21:50                                                   ` Maciej Sobczak
  2007-09-26 22:20                                                     ` Ray Blaak
  2007-09-27  0:01                                                     ` Randy Brukardt
@ 2007-09-27 13:39                                                     ` Robert A Duff
  2007-09-27 14:54                                                       ` Dmitry A. Kazakov
  2007-09-27 21:23                                                       ` Maciej Sobczak
  2007-09-28 19:02                                                     ` Robert A Duff
  3 siblings, 2 replies; 74+ messages in thread
From: Robert A Duff @ 2007-09-27 13:39 UTC (permalink / raw)


Maciej Sobczak <see.my.homepage@gmail.com> writes:

> My claim in this lengthy and philosophical discussion is that C++
> provides a complete solution which can be shortly described like this:
> the final type is not established from the very beginning, but is
> progressively moving along the inheritance chain as the subsequent
> constructors of base types complete their work. Thank's to this,
> primitive operations never dispatch further than what can be
> statically reasoned about.

Thanks for summarizing the discussion for me!

I agree with you that during construction you shouldn't dispatch to the
deeper type(s) -- i.e. the ones that can see parts of the object that
are not yet initialized.

But I find the C++ solution less than ideal -- the programmer might well
expect to dispatch to the deeper type, and be surprised when it doesn't.

Question: why do you want to dispatch at all during construction?
I think I remember you saying you wanted to install the object in some
global data structure.  But that just means you want to create an
access-to-class-wide, and put that somewhere -- no need to dispatch.

In other words, would you be happy with a rule saying you can't dispatch
during construction?  (Never mind, for the moment, whether that's a
compile-time or run-time rule (or some of both).)  If the answer is
"no", please give a realistic example where dispatching during
construction makes sense.  (I apologize if you already did -- as I said,
I got lost in this thread, and I didn't read all of it.)

- Bob



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

* Re: Inherited Methods and such
  2007-09-27 13:39                                                     ` Robert A Duff
@ 2007-09-27 14:54                                                       ` Dmitry A. Kazakov
  2007-09-28  0:35                                                         ` Randy Brukardt
  2007-09-27 21:23                                                       ` Maciej Sobczak
  1 sibling, 1 reply; 74+ messages in thread
From: Dmitry A. Kazakov @ 2007-09-27 14:54 UTC (permalink / raw)


On Thu, 27 Sep 2007 09:39:46 -0400, Robert A Duff wrote:

>  If the answer is
> "no", please give a realistic example where dispatching during
> construction makes sense.

You construct a GUI window. There is a hierarchy of. The base constructor
might wish to know what kind of border the window has.

type Window is ...;
function Get_Border_Type (X : Window) return ... is abstract;

type Dialog_Frame is new Window with private;
function Get_Border_Type (X : Dialog_Frame return ...;

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



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

* Re: Inherited Methods and such
  2007-09-26 21:31                                                   ` Maciej Sobczak
@ 2007-09-27 15:02                                                     ` Dmitry A. Kazakov
  2007-09-27 21:02                                                       ` Maciej Sobczak
  0 siblings, 1 reply; 74+ messages in thread
From: Dmitry A. Kazakov @ 2007-09-27 15:02 UTC (permalink / raw)


On Wed, 26 Sep 2007 14:31:01 -0700, Maciej Sobczak wrote:

> On 26 Wrz, 12:42, "Dmitry A. Kazakov" <mail...@dmitry-kazakov.de>
> wrote:
> 
>> No, because the specific type does not kill primitive operations it only
>> freezes them.
> 
> Not much. I can recover T'Class from T, as in the example from
> previous post.

Nonetheless, T'Class /= T. Formally, you don't recover it, you create a new
object, then you call some operation on the result, and after completion
you convert it back. The compiler may optimize memory use. For by-reference
type it could be required to do so.

> How is it possible to recover T'Class from T?

Because T inherits class-wide operations of T'Class.

> Apparently the type I see in Initialize is somehow decoupled from the
> type of the object itself. In other words, what I see in Initialize is
> not the whole "truth" about the object.
> The truth is within the object
> itself, not in the parameter of Initialize, and with appropriate
> question I can get the full answer.
> But this only means that T does not really fully determine X.

It does. It has a primitive operation that constructs T'Class from T. For a
tagged type this operation uses the tag embedded in the representation of
T. This fully determines the outcome both in Ada and C++.

> With appropriate application of the notion of view this is easy - as
> far as I'm concerned, the object is T'Class, but Initialize sees only
> the T view on it.

If Initialize were a constructor hook on T, then the language should
classify the program using the conversion operation above as erroneous.

In my view to avoid inconsistencies, the conversion T -> T'Class should
yield an object with the tag = T, no matter what the object was before,
otherwise it shall fail. The object's history is erased after dispatch, now
it is T, no matter what the embedded tag tells. For tagged types,
Constraint_Error should propagate when the tag differ from T.

Note an important difference to C++, C++ does this only upon
construction/destruction. I think that this should be done always. I think
we have discussed it before. I'll try to explain it on a different model:

Consider a specific type as a point T.
Then a class were an open interval [T, oo[.

The types algebra is built on points and open intervals. Subprograms are
defined on either points or intervals. So far, so good.

Now, what you want is to get in the constructor of some S derived from T,
something like a *closed* interval: [T, S].

But you don't have operations in the types algebra for dealing with such
beasts. And the subroutines aren't defined on them. This is inconsistent
with the setup above. Additionally to specific, polymorphic operations it
would require "finite" operations. They should be allowed outside the
constructors, i.e. they would leak out in the language for the sake of
regularity. It will be complex.

> But you claimed that views are not needed. Try to explain this without
> reinventing views. :-)

See above.

N.B. Certainly, you could play with a model where each polymorphic object
were a triple: (View_Type, Native_Type, Native_Value). Then you could try
to figure out the meaning of:

   operation Foo of S called on T as if it were U.

Good luck! IMO there is enough problems with a much simpler model.

>> The
>> result of this conversion would be wrong if Initialize were a hook on the
>> constructor of T. But it isn't. So formally everything is OK. Initialize is
>> a hook on the constructor of T'Class which is officially constructed, no
>> matter that S's Initialize wasn't called.
> 
> None of Initializes was called before T'Class was complete. That's the
> problem. It's entirely reversed, leading to untyped mess.

*If* Initialize were a constructor.

>> Note that the case you presented is just a variant of the more general
>> problem called "re-dispatch." As your example nicely illustrates,
>> re-dispatch is inherently inconsistent.
> 
> I'm not sure whether this is inherent, or just a consequence of the
> fact that T'Class is formally complete before it's initialized.

It is a consequence of contract violation. T is specific, you may not
exploit its tag, without being punished.

>>> I want to register T'Class, but I don't want to involve those who
>>> derive from T.
>>
>> Sorry, but T'Class includes them, your design is inconsistent with that.
>> Can you explain where could it be useful?
> 
> Abstract factories perhaps?
> I want to set up a framework for the abstract factory design pattern.
> The base types and registry are part of the framework and the user
> provides the concrete factories. The base types register the factories
> in the registry so that later the user can request them and get
> concrete behaviour.
> This is cool and clean only if the registry mechanics is encapsulated
> within the framework, so that the user does not have to care about it
> in his own constructors - but this is possible only if the base type
> can register T'Class, not just T.

Register from the constructor of T'Class. Factory were then an abstract
primitive operation of T.

>>>> You could say it simpler: Children and Adult are different types. Period.
>>
>>> They are both in Human'Class. You try to escape the problem instead of
>>> solving it.
>>
>> I don't see any. If they are in Human'Class <=> they have the behavior of.
>> A client would use that behavior. Why do you need to change anything?
> 
> A client can ask for the additional interface after discovering the
> concrete type.

It is called dispatching, no need in changing type. Dispatching does that
for you.

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



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

* Re: Inherited Methods and such
  2007-09-27 15:02                                                     ` Dmitry A. Kazakov
@ 2007-09-27 21:02                                                       ` Maciej Sobczak
  0 siblings, 0 replies; 74+ messages in thread
From: Maciej Sobczak @ 2007-09-27 21:02 UTC (permalink / raw)


On 27 Wrz, 17:02, "Dmitry A. Kazakov" <mail...@dmitry-kazakov.de>
wrote:

> > I can recover T'Class from T, as in the example from
> > previous post.
>
> Nonetheless, T'Class /= T. Formally, you don't recover it, you create a new
> object, then you call some operation on the result, and after completion
> you convert it back.

No. I can have an access variable to the object and I can observe
modifications using this additional alias. This means that no new
object is created.

> The compiler may optimize memory use. For by-reference
> type it could be required to do so.

If it's required to optimize, then it's not optimization, but
semantics. There is no new object.

> > How is it possible to recover T'Class from T?
>
> Because T inherits class-wide operations of T'Class.

Too bad. T'Class can be {T, S} or {T, S, U}, or ... and there is
observable difference between them, so that redispatching on T might
give different behaviours, which means that T /= T. Oops.

> It has a primitive operation that constructs T'Class from T. For a
> tagged type this operation uses the tag embedded in the representation of
> T.

Then you admit that there is something more than just T - the object
is involved. You have just decoupled T from object. Still without
views? :-)

> In my view to avoid inconsistencies, the conversion T -> T'Class should
> yield an object with the tag = T, no matter what the object was before,
> otherwise it shall fail.

I don't understand it. What is "otherwise"? There is no condition
before that part.

> Note an important difference to C++, C++ does this only upon
> construction/destruction. I think that this should be done always. I think
> we have discussed it before. I'll try to explain it on a different model:
>
> Consider a specific type as a point T.
> Then a class were an open interval [T, oo[.
>
> The types algebra is built on points and open intervals. Subprograms are
> defined on either points or intervals. So far, so good.
>
> Now, what you want is to get in the constructor of some S derived from T,
> something like a *closed* interval: [T, S].
>
> But you don't have operations in the types algebra for dealing with such
> beasts. And the subroutines aren't defined on them. This is inconsistent
> with the setup above. Additionally to specific, polymorphic operations it
> would require "finite" operations. They should be allowed outside the
> constructors, i.e. they would leak out in the language for the sake of
> regularity. It will be complex.

Yes, but this assumes that these types would be needed outside of
constructors. I mean - outside of the temporal frame when this
actually matters. C++ avoids this complexity by limiting the time
frame when this phenomenon occurs and uses open intervals during the
"regular" lifetime of the object.
Is it inconsistent? Yes, if the static type algebra is requested -
that's your point as I understand.
Does it work "properly" when expected? Yes, for some reasonable
definition of "properly".

> > But you claimed that views are not needed. Try to explain this without
> > reinventing views. :-)
>
> See above.

T /= T

Recovering T'Class from T is not in T's interface. It's the object
that does it. There is some necessary magic in redispatch that
bypasses T and get's to the object directly (inconsistency), but T
itself does not have this operation.


> > None of Initializes was called before T'Class was complete. That's the
> > problem. It's entirely reversed, leading to untyped mess.
>
> *If* Initialize were a constructor.

Exactly. The point is that without constructors we have to resort the
the next closest match. Even if that leads to mess.


> > I want to set up a framework for the abstract factory design pattern.

> Register from the constructor of T'Class.

I don't want to, because that exposes the registry to the developers
of T'Class and it was supposed to be encapsulated part of the
framework.
Last but not least - what if the developer of T'Class forgets to
register? How can I force it?

Registering from the base type is more straightforward - and safer.

> > A client can ask for the additional interface after discovering the
> > concrete type.
>
> It is called dispatching, no need in changing type. Dispatching does that
> for you.

No, it does not give the *additional* interface.

--
Maciej Sobczak
http://www.msobczak.com/




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

* Re: Inherited Methods and such
  2007-09-26 23:47                                                     ` Randy Brukardt
@ 2007-09-27 21:08                                                       ` Maciej Sobczak
  2007-09-28  0:44                                                         ` Randy Brukardt
  0 siblings, 1 reply; 74+ messages in thread
From: Maciej Sobczak @ 2007-09-27 21:08 UTC (permalink / raw)


On 27 Wrz, 01:47, "Randy Brukardt" <ra...@rrsoftware.com> wrote:

> No, the problem is quite obviously with T. (Re)-dispatching within
> Initialize is a sure-fire way to have a disaster.

1. I don't have to redispatch from Initialize, I can do this from
subprogram S1, which was called from S2, which was called from ... SN,
which was called from Initialize.

2. The fact that there is a disaster is exactly my point. The
interpretation differs, though. :-)

> I wouldn't go as far as Dmitry
> and disallow all redispatching, but it surely should not happen until the
> object is fully constructed.

Bingo. The only "issue" is that Ada does not acknowledge this
"progressive" nature of construction and there is no recognized place
for being not fully constructed.

> (I suspect the same is true while it is being
> finalized

Yes. The tag should progressively "degrade" during subsequent stages
of finalization.

--
Maciej Sobczak
http://www.msobczak.com/




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

* Re: Inherited Methods and such
  2007-09-27 13:39                                                     ` Robert A Duff
  2007-09-27 14:54                                                       ` Dmitry A. Kazakov
@ 2007-09-27 21:23                                                       ` Maciej Sobczak
  2007-09-28 19:12                                                         ` Robert A Duff
  1 sibling, 1 reply; 74+ messages in thread
From: Maciej Sobczak @ 2007-09-27 21:23 UTC (permalink / raw)


On 27 Wrz, 15:39, Robert A Duff <bobd...@shell01.TheWorld.com> wrote:

> I agree with you that during construction you shouldn't dispatch to the
> deeper type(s) -- i.e. the ones that can see parts of the object that
> are not yet initialized.

Exactly.

> But I find the C++ solution less than ideal -- the programmer might well
> expect to dispatch to the deeper type, and be surprised when it doesn't.

Of course. I can assure you that this question is quite regular and
even has its own FAQ entry:

http://www.parashift.com/c++-faq-lite/strange-inheritance.html#faq-23.5

> Question: why do you want to dispatch at all during construction?

I don't. And I can assure you that I don't remember any piece of code
where that would actually matter to me. But Dmitry wanted to discuss
completeness. ;-)

> I think I remember you saying you wanted to install the object in some
> global data structure.  But that just means you want to create an
> access-to-class-wide, and put that somewhere -- no need to dispatch.

Yes, but this was taken out of some fragment of this lengthy
discussion. Registration requires that T'Class is available in the
base type constructor.

So - even if I don't need to dispatch, I might need T'Class.

> In other words, would you be happy with a rule saying you can't dispatch
> during construction?

I would be perfectly fine with this rule, but having this rule *and*
access to T'Class are not much compatible with each other. How can you
prevent me from dispatching if I already have T'Class? With runtime
checks, perhaps? That's the only reasonable option (injecting
artificial types is not reasonable, even if compelling for theorists),
although it would mean that some type provides the interface that
changes with time. I understand Dmitry's arguments against it.

--
Maciej Sobczak
http://www.msobczak.com/




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

* Re: Inherited Methods and such
  2007-09-27 14:54                                                       ` Dmitry A. Kazakov
@ 2007-09-28  0:35                                                         ` Randy Brukardt
       [not found]                                                           ` <7p6gc1s9imfa$.kmvwf5zyf8e9.dlg@40tude.net>
  0 siblings, 1 reply; 74+ messages in thread
From: Randy Brukardt @ 2007-09-28  0:35 UTC (permalink / raw)


"Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> wrote in message
news:1tovavprv3hg2$.w3sgselxd5ob$.dlg@40tude.net...
> On Thu, 27 Sep 2007 09:39:46 -0400, Robert A Duff wrote:
>
> >  If the answer is
> > "no", please give a realistic example where dispatching during
> > construction makes sense.
>
> You construct a GUI window. There is a hierarchy of. The base constructor
> might wish to know what kind of border the window has.
>
> type Window is ...;
> function Get_Border_Type (X : Window) return ... is abstract;
>
> type Dialog_Frame is new Window with private;
> function Get_Border_Type (X : Dialog_Frame return ...;

This doesn't begin to make sense. The object hasn't even found out what it's
border it (because it hasn't been constructed), and you want to ask what it
is?

Dispatching in a constructor is a bug, period. It's unfortunate that Ada
can't detect it (and I'm unconvinced that an implementable runtime check
could be made without distributed overhead). But there are a lot of bugs
that Ada doesn't try to detect (uninitialized components, for instance), so
I can't get that excited about this one.

                       Randy.





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

* Re: Inherited Methods and such
  2007-09-27 21:08                                                       ` Maciej Sobczak
@ 2007-09-28  0:44                                                         ` Randy Brukardt
  2007-09-28 20:32                                                           ` Maciej Sobczak
  0 siblings, 1 reply; 74+ messages in thread
From: Randy Brukardt @ 2007-09-28  0:44 UTC (permalink / raw)


"Maciej Sobczak" <see.my.homepage@gmail.com> wrote in message
news:1190927328.753361.138630@d55g2000hsg.googlegroups.com...
> On 27 Wrz, 01:47, "Randy Brukardt" <ra...@rrsoftware.com> wrote:
>
> > No, the problem is quite obviously with T. (Re)-dispatching within
> > Initialize is a sure-fire way to have a disaster.
>
> 1. I don't have to redispatch from Initialize, I can do this from
> subprogram S1, which was called from S2, which was called from ... SN,
> which was called from Initialize.

Of course, that's why it can't be banned. But it is still wrong. I would go
so far as to suggest that *calling out* from Initialize with an object of
the type (or an ancestor) is wrong (with the single exception of calling a
parent's Initialize routine). But that might be going too far. (Still, that
is a rule that could be implemented by a style checker; it wouldn't be
possible for the language to do it because the body of Initialize doesn't
have to be explicitly declared: it could be a rename of something that is
used also for other purposes.).

> 2. The fact that there is a disaster is exactly my point. The
> interpretation differs, though. :-)

Doing something stupid often causes disasters. Education is often needed to
prevent them; it's not possible for a compiler to prevent them all.

> > I wouldn't go as far as Dmitry
> > and disallow all redispatching, but it surely should not happen until
the
> > object is fully constructed.
>
> Bingo. The only "issue" is that Ada does not acknowledge this
> "progressive" nature of construction and there is no recognized place
> for being not fully constructed.
>
> > (I suspect the same is true while it is being
> > finalized
>
> Yes. The tag should progressively "degrade" during subsequent stages
> of finalization.

No reason for messing with the tag or doing it "progressively". If
dispatching is wrong on Initialize, it is wrong in all of them, so it would
be banned until construction is finished, and similarly during destruction.
Construction and destruction are still single events: there is no
interruption in those events to do other things. (Remember that finalization
is even abort-deferred.) Even though the event of construction may include
many operations, it's still indivisible from the point of view of the
object.

                      Randy.





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

* Re: Inherited Methods and such
  2007-09-26 21:50                                                   ` Maciej Sobczak
                                                                       ` (2 preceding siblings ...)
  2007-09-27 13:39                                                     ` Robert A Duff
@ 2007-09-28 19:02                                                     ` Robert A Duff
  2007-09-28 19:42                                                       ` Robert A Duff
  3 siblings, 1 reply; 74+ messages in thread
From: Robert A Duff @ 2007-09-28 19:02 UTC (permalink / raw)


Maciej Sobczak <see.my.homepage@gmail.com> writes:

> On 26 Wrz, 14:26, Robert A Duff <bobd...@shell01.TheWorld.com> wrote:
>> And why "constructor functions" do not solve
>> it?
>
> Constructor functions in Ada are functions that return new objects
> (most useful for limited types since Ada 2005) can have parameters,
> but they still don't acknowledge the "progressive" nature of the whole
> process.

I think they do, actually.  At least, they can, if you program it that
way.  You can say (presuming T2 is derived from T1):

    function Make_T2 (...) return T2 is
    begin
        return Result : T2 := (Make_T1 (...) with ...) do
            ...
        end return;
    end Make_T2;

Within Make_T1, the 'Tag of its result will be T1'Tag,
even though that is really the parent part of the Result in T2,
whose Tag is T2'Tag.  And a call to this Make_T2 could
be the parent part of some other extension aggregate
in some other constructor function, with some other
(deeper) Tag.

>... This means that no matter whether we use Initialize or
> constructor functions, the final type of the object is established at
> the very beginning and is kept during the whole construction process.

You're right about Initialize, and I agree with you (I don't much like
it).  But constructor functions are different.

> If this process involves some activities on various levels of the type
> inheritance chain, the ugly effects like the one from my example can
> result, because primitive operations can be called bypassing the
> intended order of component construction.
>
>> Are there other languages (c++, perhaps?) that have a complete solution
>> to this problem?
>
> My claim in this lengthy and philosophical discussion is that C++
> provides a complete solution which can be shortly described like this:
> the final type is not established from the very beginning, but is
> progressively moving along the inheritance chain as the subsequent
> constructors of base types complete their work. Thank's to this,
> primitive operations never dispatch further than what can be
> statically reasoned about.

Well, I'd say that Ada is doing the same thing in my above example,
except it's not "changing" the Tag of a single object -- instead,
the result of Make_T1 has its Tag, and then Make_T2 has a different
Tag.  Within Make_T1, you can't dispatch to a T2 operation.

> Implementation-wise, this can be done with "just" changing the tag of
> the object as each base constructor completes.

Right.  Implementation-wise, the Tag of the result of Make_T1 is stored
at the same location in memory as the Tag of the result of Make_T2.
This memory location is overwritten after Make_T1 returns,
during the evaluation of the extension aggregate.  (Well, to
be precise, it's the same location if the type is limited.
For nonlimited, the implementation can choose to make them
the same location, or can copy things.)

- Bob



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

* Re: Inherited Methods and such
  2007-09-27 21:23                                                       ` Maciej Sobczak
@ 2007-09-28 19:12                                                         ` Robert A Duff
  0 siblings, 0 replies; 74+ messages in thread
From: Robert A Duff @ 2007-09-28 19:12 UTC (permalink / raw)


Maciej Sobczak <see.my.homepage@gmail.com> writes:

> On 27 Wrz, 15:39, Robert A Duff <bobd...@shell01.TheWorld.com> wrote:
>> But I find the C++ solution less than ideal -- the programmer might well
>> expect to dispatch to the deeper type, and be surprised when it doesn't.
>
> Of course. I can assure you that this question is quite regular and
> even has its own FAQ entry:
>
> http://www.parashift.com/c++-faq-lite/strange-inheritance.html#faq-23.5
>
>> Question: why do you want to dispatch at all during construction?
>
> I don't.

OK, good.  So neither C++ nor Ada is ideal here, because both allow
dispatching during construction.  (And for Initialize, Ada is arguably
worse than C++, since it allows dispatching to the deeper type.)

> Yes, but this was taken out of some fragment of this lengthy
> discussion. Registration requires that T'Class is available in the
> base type constructor.
>
> So - even if I don't need to dispatch, I might need T'Class.

Interesting.  I'm not sure what the best language design would be, for
this sort of thing.

>> In other words, would you be happy with a rule saying you can't dispatch
>> during construction?
>
> I would be perfectly fine with this rule, but having this rule *and*
> access to T'Class are not much compatible with each other.

Indeed.

>... How can you
> prevent me from dispatching if I already have T'Class? With runtime
> checks, perhaps?

In the Ada context, a run-time check is the only way I can think of.

- Bob



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

* Re: Inherited Methods and such
  2007-09-28 19:02                                                     ` Robert A Duff
@ 2007-09-28 19:42                                                       ` Robert A Duff
  2007-09-28 20:44                                                         ` Maciej Sobczak
  0 siblings, 1 reply; 74+ messages in thread
From: Robert A Duff @ 2007-09-28 19:42 UTC (permalink / raw)


Robert A Duff <bobduff@shell01.TheWorld.com> writes:

> Maciej Sobczak <see.my.homepage@gmail.com> writes:
>> Constructor functions in Ada are functions that return new objects
>> (most useful for limited types since Ada 2005) can have parameters,
>> but they still don't acknowledge the "progressive" nature of the whole
>> process.
>
> I think they do, actually.

Here's an example.  What do you think?  Do you revise your opinion that
Ada doesn't properly support constructors?

We've got a Root_Window type, and a type derived from that,
for "red" windows (OK, I didn't bother with something more
realistic).

Note that this example dispatches during construction,
and dispatches the way C++ constructors would (shallow,
not deep).  But I think we both agree that dispatching
at all during construction is questionable.

(There's an AI on this issue, by the way.)

Maybe Root_Window should have unknown discriminants (<>),
to force the use of the provided constructors.

I get this output, using the latest (internal to AdaCore) version of
GNAT:

Evilly dispatching from Make_Root_Window.
Hello from Class_Wide
Hello from Primitive (Root_Window)
Goodbye from Class_Wide
Evilly dispatching from Make_Red_Window.
Hello from Class_Wide
Hello from Primitive (Red_Window)
Goodbye from Class_Wide
Hello from Class_Wide
Hello from Primitive (Red_Window)
Goodbye from Class_Wide

And here's the code:

with Ada.Text_IO; use Ada.Text_IO;
package Root_Windows is

   type Root_Window is tagged limited private;

   procedure Primitive (Win : Root_Window);
   procedure Class_Wide (Win : Root_Window'Class);

private

   type Root_Window is tagged limited
      record
         X : Integer;
      end record;

end Root_Windows;

package Root_Windows.Factory is

   function Make_Root_Window (X : Integer) return Root_Window;

end Root_Windows.Factory;

with Ada.Text_IO; use Ada.Text_IO;
with Root_Windows; use Root_Windows;
package Red_Windows is

   type Red_Window is new Root_Window with private;

   procedure Primitive (Win : Red_Window);

private

   type Red_Window is new Root_Window with
      record
         Y : Integer;
      end record;

end Red_Windows;

package Red_Windows.Factory is

   function Make_Red_Window (X, Y : Integer) return Red_Window;

end Red_Windows.Factory;

package body Root_Windows is

   procedure Primitive (Win : Root_Window) is
   begin
      Put_Line ("Hello from Primitive (Root_Window)");
   end Primitive;

   procedure Class_Wide (Win : Root_Window'Class) is
   begin
      Put_Line ("Hello from Class_Wide");
      Primitive (Win); -- dispatch
      Put_Line ("Goodbye from Class_Wide");
   end Class_Wide;

end Root_Windows;

package body Root_Windows.Factory is

   function Make_Root_Window (X : Integer) return Root_Window is
   begin
      return Result : Root_Window := (X => X) do
         Put_Line ("Evilly dispatching from Make_Root_Window.");
         Class_Wide (Result);
      end return;
   end Make_Root_Window;

end Root_Windows.Factory;

package body Red_Windows is

   procedure Primitive (Win : Red_Window) is
   begin
      Put_Line ("Hello from Primitive (Red_Window)");
   end Primitive;

end Red_Windows;

with Root_Windows.Factory;
package body Red_Windows.Factory is

   function Make_Red_Window (X, Y : Integer) return Red_Window is
   begin
      return Result : Red_Window :=
        (Root_Windows.Factory.Make_Root_Window (X) with Y => Y) do

         Put_Line ("Evilly dispatching from Make_Red_Window.");
         Class_Wide (Result);
      end return;
   end Make_Red_Window;

end Red_Windows.Factory;

with Red_Windows.Factory;
procedure Red_Windows.Test is
   My_Window : Root_Window'Class := Factory.Make_Red_Window (X => 1, Y => 2);
begin
   Class_Wide (My_Window);
end Red_Windows.Test;




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

* Re: Inherited Methods and such
  2007-09-28  0:44                                                         ` Randy Brukardt
@ 2007-09-28 20:32                                                           ` Maciej Sobczak
  2007-09-28 22:35                                                             ` Randy Brukardt
  2007-09-29 23:58                                                             ` Robert A Duff
  0 siblings, 2 replies; 74+ messages in thread
From: Maciej Sobczak @ 2007-09-28 20:32 UTC (permalink / raw)


On 28 Wrz, 02:44, "Randy Brukardt" <ra...@rrsoftware.com> wrote:

> > 1. I don't have to redispatch from Initialize, I can do this from
> > subprogram S1, which was called from S2, which was called from ... SN,
> > which was called from Initialize.
>
> Of course, that's why it can't be banned. But it is still wrong. I would go
> so far as to suggest that *calling out* from Initialize with an object of
> the type (or an ancestor) is wrong (with the single exception of calling a
> parent's Initialize routine). But that might be going too far.

It might be good as a coding convention, though.

> (Still, that
> is a rule that could be implemented by a style checker

Yes.


> > > (I suspect the same is true while it is being
> > > finalized
>
> > Yes. The tag should progressively "degrade" during subsequent stages
> > of finalization.
>
> No reason for messing with the tag or doing it "progressively". If
> dispatching is wrong on Initialize, it is wrong in all of them, so it would
> be banned until construction is finished, and similarly during destruction.

Just have said yourself that it cannot be banned.
What about aliasing the object? I don't have to dispatch from
Initialize directly - I can leak the reference to some global access
variable and some other part of the code can pick it from there. A
mess? Sure, but we are talking about completeness.

BTW - your argument about covering this issue with education is
dangerous - someone might use it against you the next time anybody
criticizes "other" languages for being error prone... ;-)

--
Maciej Sobczak
http://www.msobczak.com/




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

* Re: Inherited Methods and such
  2007-09-28 19:42                                                       ` Robert A Duff
@ 2007-09-28 20:44                                                         ` Maciej Sobczak
  2007-09-28 22:40                                                           ` Randy Brukardt
                                                                             ` (3 more replies)
  0 siblings, 4 replies; 74+ messages in thread
From: Maciej Sobczak @ 2007-09-28 20:44 UTC (permalink / raw)


On 28 Wrz, 21:42, Robert A Duff <bobd...@shell01.TheWorld.com> wrote:

> Here's an example.  What do you think?

Perfect! I wasn't aware of the possibility to use return to achieve
this "progressive" construction process.
I knew this form of return, but not the gory details of how tags are
assigned.

> Do you revise your opinion that
> Ada doesn't properly support constructors?

Yes, this example is convincing.
How would you recommend using this pattern with controlled types?

I might need controlled for the finalization part - do you recommend
ignoring the Initialize operation in this case?

--
Maciej Sobczak
http://www.msobczak.com/





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

* Re: Inherited Methods and such
  2007-09-28 20:32                                                           ` Maciej Sobczak
@ 2007-09-28 22:35                                                             ` Randy Brukardt
  2007-09-29 23:58                                                             ` Robert A Duff
  1 sibling, 0 replies; 74+ messages in thread
From: Randy Brukardt @ 2007-09-28 22:35 UTC (permalink / raw)


"Maciej Sobczak" <see.my.homepage@gmail.com> wrote in message
news:1191011579.746176.80370@o80g2000hse.googlegroups.com...

> BTW - your argument about covering this issue with education is
> dangerous - someone might use it against you the next time anybody
> criticizes "other" languages for being error prone... ;-)

I actually worried about that when I wrote it, but I left it because
expecting 100% detection of errors isn't possible; there are always some
that will get through. And it's not clear to me that anyone would use such a
language. But that doesn't excuse languages that only detect 10% of common
errors. Ada strikes nearly the right balance (although I'd like the option
to detect of uninitialized objects to be required; pragma Normalize_Scalars
is both optional and doesn't detect all such objects).

                              Randy.







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

* Re: Inherited Methods and such
  2007-09-28 20:44                                                         ` Maciej Sobczak
@ 2007-09-28 22:40                                                           ` Randy Brukardt
  2007-09-29 20:35                                                           ` Dmitry A. Kazakov
                                                                             ` (2 subsequent siblings)
  3 siblings, 0 replies; 74+ messages in thread
From: Randy Brukardt @ 2007-09-28 22:40 UTC (permalink / raw)


"Maciej Sobczak" <see.my.homepage@gmail.com> wrote in message
news:1191012272.457766.273330@57g2000hsv.googlegroups.com...
...
> Yes, this example is convincing.
> How would you recommend using this pattern with controlled types?
>
> I might need controlled for the finalization part - do you recommend
> ignoring the Initialize operation in this case?

If you have first-class constructor function(s), you don't need Initialize.
Using both is likely to cause confusion. Especially as it will not be called
for an aggregate, so if it does anything valuable, it won't happen in those
cases.

                 Randy.





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

* Re: Inherited Methods and such
       [not found]                                                           ` <7p6gc1s9imfa$.kmvwf5zyf8e9.dlg@40tude.net>
@ 2007-09-28 22:53                                                             ` Randy Brukardt
  2007-09-29 20:37                                                               ` Dmitry A. Kazakov
  0 siblings, 1 reply; 74+ messages in thread
From: Randy Brukardt @ 2007-09-28 22:53 UTC (permalink / raw)


"Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> wrote in message
news:7p6gc1s9imfa$.kmvwf5zyf8e9.dlg@40tude.net...
...
>> This doesn't begin to make sense. The object hasn't even found out what
it's
>> border it (because it hasn't been constructed), and you want to ask what
it
>> is?

>Surely. The base type does not know what border it has. It only knows that
>there is one and it wants to ask for. This is nothing special, it is an
>abstract primitive operation.

But there is no reason for the base window to do anything with the border,
because it knows nothing about the implementation. That information surely
can have nothing to do with the construction of the base window (otherwise
you have a serious data coupling problem), so there is no reason for the
window to ask about it during construction.

> > Dispatching in a constructor is a bug, period.
>
> No, that depends on what is constructor and what is being constructed.
>
> The point is that it is perfectly reasonable and consistent to dispatch at
> the end of construction of T'Class, It is wrong to dispatch before that.

It makes sense to dispatch *after* the end of the construction, not at any
point during it. Any operation for which this makes sense cannot have
anything to do with construction. Mixing such operations up with
constructors is just confusing, and certainly bad design.

> Further, Ada.Finalization.Initialize already *dispatches* right from
there.
> It seemingly made sense for the designers of Ada 95. Why Get_Border_Type
> does not?

Because it was a hack to provide a hook. Just because it is in the language
doesn't make it good design! The whole of Ada.Finalization.Controlled is a
hack - the dispatching is just a cover for a lack of will to include the
proper magic for hooks. And because it is a hack, you can starting asking
questions that are inappropriate. I think it is best to ignore Initialize as
much as possible, and for Ada 2007, use constructor functions instead.

                                    Randy.





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

* Re: Inherited Methods and such
  2007-09-28 20:44                                                         ` Maciej Sobczak
  2007-09-28 22:40                                                           ` Randy Brukardt
@ 2007-09-29 20:35                                                           ` Dmitry A. Kazakov
  2007-09-29 20:52                                                             ` Maciej Sobczak
  2007-09-29 23:47                                                             ` Robert A Duff
  2007-09-29 20:48                                                           ` Maciej Sobczak
  2007-09-29 23:42                                                           ` Robert A Duff
  3 siblings, 2 replies; 74+ messages in thread
From: Dmitry A. Kazakov @ 2007-09-29 20:35 UTC (permalink / raw)


On Fri, 28 Sep 2007 13:44:32 -0700, Maciej Sobczak wrote:

> On 28 Wrz, 21:42, Robert A Duff <bobd...@shell01.TheWorld.com> wrote:
> 
>> Here's an example.  What do you think?
> 
> Perfect! I wasn't aware of the possibility to use return to achieve
> this "progressive" construction process.

It is not return, the job is done by extension aggregates, these are Ada 95
by the way. C++'s

   Derived () : Base (), Component () {}

is equivalent to Ada's

   Derived'(Base with Component);

What Ada 2005 return adds, is a non-empty body of, i.e. the stuff between
{...} is do ... end return;

Then Ada 2005 allowed aggregates for limited types.

> I knew this form of return, but not the gory details of how tags are
> assigned.

The tag is set to reflect the object's type, what did you expect? (:-)) But
note, nothing is mutating in the example.

>> Do you revise your opinion that
>> Ada doesn't properly support constructors?
> 
> Yes, this example is convincing.

No, it is not. Because aggregate is not a replacement for user-defined
constructors.

1. It breaks encapsulation as you should make the record nature of type
public.

2. The base type cannot enforce vital construction code, when records are
exposed.

3. Nothing can be done for destructors.

4. Dispatch upon construction completion and before destruction beginning
is still impossible.

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



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

* Re: Inherited Methods and such
  2007-09-28 22:53                                                             ` Randy Brukardt
@ 2007-09-29 20:37                                                               ` Dmitry A. Kazakov
  0 siblings, 0 replies; 74+ messages in thread
From: Dmitry A. Kazakov @ 2007-09-29 20:37 UTC (permalink / raw)


On Fri, 28 Sep 2007 17:53:47 -0500, Randy Brukardt wrote:

> "Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> wrote in message
> news:7p6gc1s9imfa$.kmvwf5zyf8e9.dlg@40tude.net...
> ...
>>> This doesn't begin to make sense. The object hasn't even found out what it's
>>> border it (because it hasn't been constructed), and you want to ask what it
>>> is?
> 
>>Surely. The base type does not know what border it has. It only knows that
>>there is one and it wants to ask for. This is nothing special, it is an
>>abstract primitive operation.
> 
> But there is no reason for the base window to do anything with the border,
> because it knows nothing about the implementation.

Uhm, but this what ADT is about. The client knows nothing about the
implementation.

The abstract window might ask its implementation for concrete border
thickness, for example.

>>> Dispatching in a constructor is a bug, period.
>>
>> No, that depends on what is constructor and what is being constructed.
>>
>> The point is that it is perfectly reasonable and consistent to dispatch at
>> the end of construction of T'Class, It is wrong to dispatch before that.
> 
> It makes sense to dispatch *after* the end of the construction, not at any
> point during it. Any operation for which this makes sense cannot have
> anything to do with construction. Mixing such operations up with
> constructors is just confusing, and certainly bad design.

The point is that it is construction of T'Class. Because tagged types in
Ada share the implementation with their classes, a construction of, would
also construct the class. If classes were also allowed for non-tagged
types, the constructor of T'Class would be called upon each substitution of
T for T'Class.

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



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

* Re: Inherited Methods and such
  2007-09-28 20:44                                                         ` Maciej Sobczak
  2007-09-28 22:40                                                           ` Randy Brukardt
  2007-09-29 20:35                                                           ` Dmitry A. Kazakov
@ 2007-09-29 20:48                                                           ` Maciej Sobczak
  2007-09-29 23:39                                                             ` Robert A Duff
  2007-09-29 23:42                                                           ` Robert A Duff
  3 siblings, 1 reply; 74+ messages in thread
From: Maciej Sobczak @ 2007-09-29 20:48 UTC (permalink / raw)


On 28 Wrz, 22:44, Maciej Sobczak <see.my.homep...@gmail.com> wrote:

> Yes, this example is convincing.

After thinking about it for a while, I see one point missing:
accessing T'Class from the supertype (T's) constructor. I need it to
register the new object in the polymorphic container.

--
Maciej Sobczak
http://www.msobczak.com/




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

* Re: Inherited Methods and such
  2007-09-29 20:35                                                           ` Dmitry A. Kazakov
@ 2007-09-29 20:52                                                             ` Maciej Sobczak
  2007-09-30  8:38                                                               ` Dmitry A. Kazakov
  2007-09-29 23:47                                                             ` Robert A Duff
  1 sibling, 1 reply; 74+ messages in thread
From: Maciej Sobczak @ 2007-09-29 20:52 UTC (permalink / raw)


On 29 Wrz, 22:35, "Dmitry A. Kazakov" <mail...@dmitry-kazakov.de>
wrote:

> > Yes, this example is convincing.
>
> No, it is not. Because aggregate is not a replacement for user-defined
> constructors.
>
> 1. It breaks encapsulation as you should make the record nature of type
> public.

Why it would make any difference?
The type is tagged and this is known. If it's tagged, then it has the
record nature - no way and no reason to hide it.

The record itself can be private, because the constructor function is
presumably in the same package.

> 2. The base type cannot enforce vital construction code, when records are
> exposed.

I don't understand this part.

> 3. Nothing can be done for destructors.

Right.

> 4. Dispatch upon construction completion and before destruction beginning
> is still impossible.

Why?

--
Maciej Sobczak
http://www.msobczak.com/




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

* Re: Inherited Methods and such
  2007-09-29 20:48                                                           ` Maciej Sobczak
@ 2007-09-29 23:39                                                             ` Robert A Duff
  2007-09-30  8:38                                                               ` Dmitry A. Kazakov
  0 siblings, 1 reply; 74+ messages in thread
From: Robert A Duff @ 2007-09-29 23:39 UTC (permalink / raw)


Maciej Sobczak <see.my.homepage@gmail.com> writes:

> On 28 Wrz, 22:44, Maciej Sobczak <see.my.homep...@gmail.com> wrote:
>
>> Yes, this example is convincing.
>
> After thinking about it for a while, I see one point missing:
> accessing T'Class from the supertype (T's) constructor. I need it to
> register the new object in the polymorphic container.

You can convert the result object to T'Class.
You can create an access-to-T'Class value.
I don't see the problem.

But if you dispatch "too early", things can get confusing.

One limitation of my example: it doesn't work if the parent
type is abstract.  In a from-scratch language design, I think
I would solve that problem by allowing the creation of objects
of abstract type.  And constructor functions returns such
objects.  But don't allow conversion of such objects to
class-wide (to avoid dispatching (to abstract subprograms)).
What do you think about that?  It's not Ada, of course.

- Bob



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

* Re: Inherited Methods and such
  2007-09-28 20:44                                                         ` Maciej Sobczak
                                                                             ` (2 preceding siblings ...)
  2007-09-29 20:48                                                           ` Maciej Sobczak
@ 2007-09-29 23:42                                                           ` Robert A Duff
  3 siblings, 0 replies; 74+ messages in thread
From: Robert A Duff @ 2007-09-29 23:42 UTC (permalink / raw)


Maciej Sobczak <see.my.homepage@gmail.com> writes:

> On 28 Wrz, 21:42, Robert A Duff <bobd...@shell01.TheWorld.com> wrote:
>
>> Here's an example.  What do you think?
>
> Perfect! I wasn't aware of the possibility to use return to achieve
> this "progressive" construction process.
> I knew this form of return, but not the gory details of how tags are
> assigned.
>
>> Do you revise your opinion that
>> Ada doesn't properly support constructors?
>
> Yes, this example is convincing.

Good to hear.  :-)

> How would you recommend using this pattern with controlled types?
>
> I might need controlled for the finalization part - do you recommend
> ignoring the Initialize operation in this case?

Not sure.  Probably ignorable in most cases.
Note that Initialize defers abort, which might be
interesting in some programs.

As for finalization, I'm not sure it's exactly the reverse of
initialization.  For initialization, you're creating something
out of whole cloth ("raw bits").  But for finalization, you're
not turning anything into raw bits, and you can program defensively
(e.g. make sure Finalize works when called twice on the same
object).

- Bob



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

* Re: Inherited Methods and such
  2007-09-29 20:35                                                           ` Dmitry A. Kazakov
  2007-09-29 20:52                                                             ` Maciej Sobczak
@ 2007-09-29 23:47                                                             ` Robert A Duff
  1 sibling, 0 replies; 74+ messages in thread
From: Robert A Duff @ 2007-09-29 23:47 UTC (permalink / raw)


"Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> writes:

> On Fri, 28 Sep 2007 13:44:32 -0700, Maciej Sobczak wrote:
>
>> On 28 Wrz, 21:42, Robert A Duff <bobd...@shell01.TheWorld.com> wrote:
>> 
>>> Here's an example.  What do you think?
>> 
>> Perfect! I wasn't aware of the possibility to use return to achieve
>> this "progressive" construction process.
>
> It is not return, the job is done by extension aggregates, these are Ada 95
> by the way. C++'s
>
>    Derived () : Base (), Component () {}
>
> is equivalent to Ada's
>
>    Derived'(Base with Component);
>
> What Ada 2005 return adds, is a non-empty body of, i.e. the stuff between
> {...} is do ... end return;

And a name for the result.

> Then Ada 2005 allowed aggregates for limited types.

Yes, aggregates and functions.

>> I knew this form of return, but not the gory details of how tags are
>> assigned.
>
> The tag is set to reflect the object's type, what did you expect? (:-)) But
> note, nothing is mutating in the example.

At the semantic level, that's right.  Internally, the Tag of the parent
is stored at the same memory location as the Tag of the child object.
So at the implementation level, the Tag is in fact changing.

>>> Do you revise your opinion that
>>> Ada doesn't properly support constructors?
>> 
>> Yes, this example is convincing.
>
> No, it is not. Because aggregate is not a replacement for user-defined
> constructors.
>
> 1. It breaks encapsulation as you should make the record nature of type
> public.

I don't understand this objection.  In my example, all types are
private (or "new ... with private").

> 2. The base type cannot enforce vital construction code, when records are
> exposed.
>
> 3. Nothing can be done for destructors.
>
> 4. Dispatch upon construction completion and before destruction beginning
> is still impossible.

My example dispatched at the end of each "return".
It's not foolproof, but it seems to get the job done.

- Bob



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

* Re: Inherited Methods and such
  2007-09-28 20:32                                                           ` Maciej Sobczak
  2007-09-28 22:35                                                             ` Randy Brukardt
@ 2007-09-29 23:58                                                             ` Robert A Duff
  1 sibling, 0 replies; 74+ messages in thread
From: Robert A Duff @ 2007-09-29 23:58 UTC (permalink / raw)


Maciej Sobczak <see.my.homepage@gmail.com> writes:

> On 28 Wrz, 02:44, "Randy Brukardt" <ra...@rrsoftware.com> wrote:
>
>> > 1. I don't have to redispatch from Initialize, I can do this from
>> > subprogram S1, which was called from S2, which was called from ... SN,
>> > which was called from Initialize.
>>
>> Of course, that's why it can't be banned. But it is still wrong.

It could be banned by a run-time check.  It seems to me that could be
implemented without distributed overhead:  initially set the tag of
the object to point to a dummy type descriptor, whose dispatch table
entries all point to a "raise-exception" procedure.  When construction
is considered complete, set the tag to the real tag.

But run-time checks are unsatisfying.  I can't see how to do it at
compile time without major violence to the Ada language design.

>... I would go
>> so far as to suggest that *calling out* from Initialize with an object of
>> the type (or an ancestor) is wrong (with the single exception of calling a
>> parent's Initialize routine). But that might be going too far.
>
> It might be good as a coding convention, though.

I think forbidding "calling out" is going too far.  One should always be
allowed to wrap up some piece of code in a subroutine.  (That's one
reason I don't like accept statements -- they have to appear physically
inside a task body, not inside a procedure called by the task body.)

> BTW - your argument about covering this issue with education is
> dangerous - someone might use it against you the next time anybody
> criticizes "other" languages for being error prone... ;-)

Indeed.  But error-pronedness is relative.

- Bob



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

* Re: Inherited Methods and such
  2007-09-29 23:39                                                             ` Robert A Duff
@ 2007-09-30  8:38                                                               ` Dmitry A. Kazakov
  0 siblings, 0 replies; 74+ messages in thread
From: Dmitry A. Kazakov @ 2007-09-30  8:38 UTC (permalink / raw)


On Sat, 29 Sep 2007 19:39:47 -0400, Robert A Duff wrote:

> One limitation of my example: it doesn't work if the parent
> type is abstract.  In a from-scratch language design, I think
> I would solve that problem by allowing the creation of objects
> of abstract type. And constructor functions returns such
> objects.  But don't allow conversion of such objects to
> class-wide (to avoid dispatching (to abstract subprograms)).
> What do you think about that?  It's not Ada, of course.

The black hole of copying non-copyable, returning already existing into
itself, now, creating impossible is swallowing the language. (:-))

I think that a construction model of
constraint->implementation->unconstrained-thing is semantically cleaner and
can work for all types.

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



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

* Re: Inherited Methods and such
  2007-09-29 20:52                                                             ` Maciej Sobczak
@ 2007-09-30  8:38                                                               ` Dmitry A. Kazakov
  0 siblings, 0 replies; 74+ messages in thread
From: Dmitry A. Kazakov @ 2007-09-30  8:38 UTC (permalink / raw)


On Sat, 29 Sep 2007 13:52:40 -0700, Maciej Sobczak wrote:

> On 29 Wrz, 22:35, "Dmitry A. Kazakov" <mail...@dmitry-kazakov.de>
> wrote:
> 
>> 1. It breaks encapsulation as you should make the record nature of type
>> public.
> 
> Why it would make any difference?
> The type is tagged and this is known. If it's tagged, then it has the
> record nature - no way and no reason to hide it.

It is a language fault that there are types which cannot be
user-constructed. The problem is that if you tried to fix the language, for
example by allowing a task object being shut down when finalized, you would
make them all records. Is it LISP or Ada?

>> 2. The base type cannot enforce vital construction code, when records are
>> exposed.
> 
> I don't understand this part.

If the object is just a record I can assemble it in any available way. And
there are many of them. You will have a difficult design problem to ensure
that all these ways do call your construction code. For example: 

Uninitialized base:

   X : Base;
begin
   return (X with ...) do ... end record;

Worked-around base:

   return (X'(Xs_Base with Something) with ...) do ... end record;

>> 4. Dispatch upon construction completion and before destruction beginning
>> is still impossible.

> Why?

It goes this way:

  return (Base with Mine) do My-take end record;

But the [class-wide] code to execute should be placed after "end record," 
IFF there is no following derived type under construction. Only compiler
magic can help here.

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



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

end of thread, other threads:[~2007-09-30  8:38 UTC | newest]

Thread overview: 74+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2007-09-17 14:26 Inherited Methods and such shaunpatterson
2007-09-17 15:11 ` Ludovic Brenta
2007-09-17 16:46   ` shaunpatterson
2007-09-17 19:07     ` Ludovic Brenta
2007-09-17 20:22   ` Maciej Sobczak
2007-09-17 21:07     ` Ludovic Brenta
2007-09-18 14:27       ` Maciej Sobczak
2007-09-18 14:27       ` Maciej Sobczak
2007-09-18 15:25         ` Dmitry A. Kazakov
2007-09-18 18:34           ` Ludovic Brenta
2007-09-18 19:29             ` Dmitry A. Kazakov
2007-09-18 19:39               ` Ludovic Brenta
2007-09-18 20:49                 ` Dmitry A. Kazakov
2007-09-18 21:10               ` Simon Wright
2007-09-18 20:39           ` Maciej Sobczak
2007-09-18 21:12             ` Dmitry A. Kazakov
2007-09-19 14:49               ` Maciej Sobczak
2007-09-19 15:16                 ` Dmitry A. Kazakov
2007-09-19 22:13                   ` Maciej Sobczak
2007-09-20  8:12                     ` Dmitry A. Kazakov
2007-09-20 13:52                       ` Maciej Sobczak
2007-09-20 16:22                         ` Dmitry A. Kazakov
2007-09-20 20:45                           ` Maciej Sobczak
2007-09-21 18:59                             ` Dmitry A. Kazakov
2007-09-21 21:02                               ` Maciej Sobczak
2007-09-22  8:48                                 ` Dmitry A. Kazakov
2007-09-22 21:53                                   ` Maciej Sobczak
2007-09-23  8:41                                     ` Dmitry A. Kazakov
2007-09-23 20:36                                       ` Maciej Sobczak
2007-09-24  9:32                                         ` Dmitry A. Kazakov
2007-09-24 15:02                                           ` Maciej Sobczak
2007-09-24 19:20                                             ` Dmitry A. Kazakov
2007-09-25 20:53                                               ` Maciej Sobczak
2007-09-26 10:42                                                 ` Dmitry A. Kazakov
2007-09-26 21:31                                                   ` Maciej Sobczak
2007-09-27 15:02                                                     ` Dmitry A. Kazakov
2007-09-27 21:02                                                       ` Maciej Sobczak
2007-09-26 12:21                                                 ` Robert A Duff
2007-09-26 12:54                                                   ` Dmitry A. Kazakov
2007-09-26 21:37                                                   ` Maciej Sobczak
2007-09-26 23:47                                                     ` Randy Brukardt
2007-09-27 21:08                                                       ` Maciej Sobczak
2007-09-28  0:44                                                         ` Randy Brukardt
2007-09-28 20:32                                                           ` Maciej Sobczak
2007-09-28 22:35                                                             ` Randy Brukardt
2007-09-29 23:58                                                             ` Robert A Duff
2007-09-26 12:26                                                 ` Robert A Duff
2007-09-26 21:50                                                   ` Maciej Sobczak
2007-09-26 22:20                                                     ` Ray Blaak
2007-09-27  0:01                                                     ` Randy Brukardt
2007-09-27 13:39                                                     ` Robert A Duff
2007-09-27 14:54                                                       ` Dmitry A. Kazakov
2007-09-28  0:35                                                         ` Randy Brukardt
     [not found]                                                           ` <7p6gc1s9imfa$.kmvwf5zyf8e9.dlg@40tude.net>
2007-09-28 22:53                                                             ` Randy Brukardt
2007-09-29 20:37                                                               ` Dmitry A. Kazakov
2007-09-27 21:23                                                       ` Maciej Sobczak
2007-09-28 19:12                                                         ` Robert A Duff
2007-09-28 19:02                                                     ` Robert A Duff
2007-09-28 19:42                                                       ` Robert A Duff
2007-09-28 20:44                                                         ` Maciej Sobczak
2007-09-28 22:40                                                           ` Randy Brukardt
2007-09-29 20:35                                                           ` Dmitry A. Kazakov
2007-09-29 20:52                                                             ` Maciej Sobczak
2007-09-30  8:38                                                               ` Dmitry A. Kazakov
2007-09-29 23:47                                                             ` Robert A Duff
2007-09-29 20:48                                                           ` Maciej Sobczak
2007-09-29 23:39                                                             ` Robert A Duff
2007-09-30  8:38                                                               ` Dmitry A. Kazakov
2007-09-29 23:42                                                           ` Robert A Duff
2007-09-25  1:59                                   ` Randy Brukardt
2007-09-25  8:59                                     ` Dmitry A. Kazakov
2007-09-25 21:02                                       ` Randy Brukardt
2007-09-26 12:42                                         ` Dmitry A. Kazakov
2007-09-18  4:03 ` Steve

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