Parameterised Types

Hans-Dieter.Dreier@materna.de Hans-Dieter.Dreier@materna.de
Mon, 17 May 1999 18:29:04 +0200


--ks8deCx4ecYBByykPj81c1ph2FEIVrHa
Content-type: text/plain; charset="ISO-8859-1"
Content-transfer-encoding: quoted-printable

>Hans-Dieter Dreier wrote:
>
>> Conceptually, for me type parameters are instance items. Strictly speaki=
ng,
>> List <Integer> and List <SomethingElse> are actually the same type, but =
each
>> of them may or may not be used in some context because their different
>> parameterization results in different constraints. Those may or may not
>> evaluate to true in some context. Example:
>> =

>> List <GeneralObject> a ;
>> List <SpecialObject> b;
>> b may be used as a data source where a can be used because in this case =
the
>> contents of a may be substituted by the contents of b. So it is legal to
>> write
>> a =3D b;
>> but it is illegal to write
>> b =3D a;
>
>This is difficult in general because of the following case.
>
>class A<X>
>   Field: X
>   ...
>
>procedure ...
>   ...
>   Variable: A<General> :=3D new A<Special>
>   Variable.Field :=3D new General
>
>Here Variable.Field gets a General object although it is only allowed a
>special object.  Generic parameters have to be invariant where they are
>used to instantiate read-write variables.  Where the X was only used for
>read-only variables, the covariant relationship you outlined would
>hold.  Where the X was only used for write-only variables, it would be
>contravariant instead.

If it is possible to detect situations like to one shown above (and I belie=
ve it is), the compiler may insert a runtime check. Though I don't like thi=
s as much as static type checks, it's better than nothing.

>> The implementation of our parser may of course *assume* that there is su=
ch a
>> constraint if a type parameter is of type type because this will most of=
ten
>> be the case. Then things may probably be simplified by making the value =
of
>> this type a part of the type name of the parametrized type (similar to n=
ame
>> decoration in C++). But if we do this, we should keep in mind that it is=
 a
>> mere simplification and not a concept in its own right.
>
>Hmm, could you elaborate?

It would construct a name for internal use from the type and the actual cla=
ss parameters, something like @A@@X@ and match those. So "Variable" is of t=
ype @A@@General@ and since that is different from @A@@Special@, types would=
 be  different and assignment would be possible only if a conversion were a=
vailable.

>> The bounds may even be changed at runtime, resulting in a reallocation. =
Some
>> programming languages allow this. I always found it very convenient to u=
se.
>
>This is possible, but it would invalidate existing references.  How is
>this usually handled?

References to the moved object are fixed up if the object cannot be realloc=
ated in-place. No problem if we don't allow <shudder> pointer-arithmetics <=
/shudder>.

>Copying GCs already have to handle this sort of
>situation.  What languages do you know that do this?

Centura (IIRC PowerBuilder as well).

>This is similar to
>being able to change an object's impl, parent impl, etc. at run-time.

Not really. The basic structure of an instance doesn't change (there are no=
 additional "names"); its length was not known at compile time anyway.

>I think some object-based inheritance languages allow the addition of
>fields at run-time, in parallel to a type downcast.

IMO that's quite another thing.

>
>I'm not sure this would always work easily in general however.  The
>canonical example of value-driven data is an array but generic value
>parameters can be used for typing as well as allocation.

Yes. The fact that allocation is dynamic must be made explicitly available =
to the compiler, like this:

Class X (iItems)
 Instance items
  ...
  Y: x [iItems]

Y would need to be the last instance item if we demand instance items' offs=
ets to be known at compile time. =


>> Contravariance is what I would allow by default.
>
>That would be the other option I'd take.  I haven't really decided
>whether contravariance or invariance is better.

If we express variance in terms of assertions, the programmer choose which =
one he wants, even on a per-instance-item basis. It could be tricky however=
 to determine in which case to apply the default, since it needs looking at=
 the semantics of each assertion. Otherwise the "natural" default would be =
"no type checking" which would not be good...

>> I'd suggest to use normal function syntax, ie write
>> AnIntegerArray =3D new Array (Integer);
>> instead of
>> AnIntegerArray =3D new Array <Integer>;
>
>Possibly.  () is usually used for construction parameters, which are
>handled differently, so I'd like a way of demonstrating to the reader
>that they are a different type of parameter, say by putting the type
>parameters first and separating them by a "|" or similar symbol.  Since
>the type parameter list is defined in a different place to the
>construction paremeter list, it would be handy to determine which list
>has been passed to incorrectly better, which would be easier if you
>separated them.

I never liked the C++ way of naming the constructor he same as the class. =

Instead, we should choose the Eiffel way and flag methods as "creation feat=
ures".

Assuming a constructor called "new", we would create a parameterized class =
like this:

X (MyClassParameter).new (MyCreationParameter)

>> The C++ template syntax makes parsing unneccessarily hard because <> are
>> used as operators too.
>
>Well I don't think this would affect a top-down parser.

Of course it does. It can cause a lot of backtracking. Just look at the fol=
lowing:

-------Snip------

template <class X> class Test
{
int operator> (X x) {return true}
int operator< (X x) {return false}

Test()
{
	Test<Test> Test =3D new Test<Test>;
	Test<Test> Test;  // now is this a comparison or a declaration?
                          // my claim is that it might be either
}
~Test ();
};
Test<int> A ();

------Snap-------

This compiles with VC++ 6.0.
You get the point why I don't like C++ <template syntax>?

Oh, BTW, I'd not allow to have any two identifiers with the same name in th=
e same scope. I simply do not believe that it adds to clarity or enriches t=
he language if you have lots of polymorphic functions with the same name th=
at may or may not do the same thing. My approach would be: (Re-)Name them d=
ifferently and use conversions instead. The above example would not be poss=
ible if C++ would stick to this rule. They could tell exactly what is a var=
iable name and what is a type name.

>A bottom up
>parser might be affected, but I doubt this is significant.  Error
>recovery could be a bit more important.

You bet!

>                            Normal Field    Subtype Param   Client Param
>Part Of Variable Type Spec       N                Y               Y
>Can Be Overriden                 Y                Y               N#
>Can Be Nonconstant               Y                N               N
>Specified At Instantiation       N*               N@              Y
>Restricted To Type Fields        N                Y               Y
>
># =3D Unless provided as a default value in the absence of client
>specification.
>* =3D Only through constructor parameters.
>@ =3D Implictly.

My idea is to not specify such exact rules, but rather see it from the usag=
e side: The programmer may specify what he wants for, say, a client paramet=
er,
as long as the compiler can distill sense out of it. If he insists on passi=
ng a string as a class parameter, so be it. There are but a few situations =
where the compiler *must* know the value of an expression, and I would stat=
e *them* as rules:

1. To determine class layout (feature offsets, vtable layout)
2. To translate feature names to offsets or other access code

I cannot think of other restrictions at the moment, but surely there are mo=
re. Maybe you can name some. Even assertions' values need not necessarily b=
e known at compile time, as long as code for the appropriate runtime check =
can be generated.

I think that this approach may lead to a much shorter set of rules than wou=
ld result if we try to deal with each case as you did, while at the same ti=
me being more liberal.

In the general case, results will be similar to your table.


>-- =

>     Matthew Tuck - Software Developer & All-Round Nice Guy
>             mailto:matty@box.net.au (ICQ #8125618)
>       Check out the Ultra programming language project!
>              http://www.box.net.au/~matty/ultra/
>


--

Regards,

Hans-Dieter Dreier
(Hans-Dieter.Dreier@materna.de)=

--ks8deCx4ecYBByykPj81c1ph2FEIVrHa
Content-type: text/plain; charset="ISO-8859-1"
Content-transfer-encoding: quoted-printable

IDENTIFIKATIONSANGABEN:
a23343a.txt IA5 DX-MAIL X.400 User Agent=

--ks8deCx4ecYBByykPj81c1ph2FEIVrHa--