Self type

# Peter Hall (18 years ago)

I was just reading the Self type proposal for the first time (proposals:self_type).

How should a compiler handle this keyword? Should it be able to substitute a concrete type at compile-time, or must the actual type be evaluated at runtime (possibly with varying results)?

To give an example of what I am getting at, would the following be an error (in strict mode)?

type A = {x:Self}; class B extends A {} var b:B = new B().x; // error: can't implicitly coerce from A to B ??

Or...

function A (){} A.prototype.f = function():Self{ return this; } function B() {} B.prototype.f = A.prototype.f;

Is this going to work as intended?

Or can't Self be used outside of the sorts of usage found in the proposal examples?

Finally, on naming, I don't think it should be upper-case. It's a keyword, not an actual type. But even "self" , like the lexical "this", more suggests an instance not a type.

Perhaps "this type" might be clearer, and more akin to the "this generator" and "this function" that have been proposed elsewhere.

Peter

# Brendan Eich (18 years ago)

On Aug 13, 2007, at 5:32 AM, Peter Hall wrote:

I was just reading the Self type proposal for the first time (proposals:self_type).

Cormac should reply authoritatively since he's the author of that
spec, but I'll have a go.

How should a compiler handle this keyword? Should it be able to substitute a concrete type at compile-time, or must the actual type be evaluated at runtime (possibly with varying results)?

Compile-time (see below).

To give an example of what I am getting at, would the following be an error (in strict mode)?

type A = {x:Self}; class B extends A {}

You can't extend a structural type with a class. Note that the type
definition just names the structural type, it does not make it a
nominal type. So its uses can be substituted: class B extends
{x:Self}, which is not legal.

var b:B = new B().x; // error: can't implicitly coerce from A to B ??

In any event, the Self identifier is bound within the type expression
{x: Self} (and we know it's a type expression because in this case
it's in the right-hand side of a type definition) to the nearest
enclosing object structural type.

Or...

function A (){} A.prototype.f = function():Self{ return this; }

Self is bound only within object structural types, so this too is not
legal.

function B() {} B.prototype.f = A.prototype.f;

Is this going to work as intended?

Or can't Self be used outside of the sorts of usage found in the proposal examples?

That's it.

Finally, on naming, I don't think it should be upper-case. It's a keyword, not an actual type. But even "self" , like the lexical "this", more suggests an instance not a type.

Hence the straw-man capitalized name.

Perhaps "this type" might be clearer, and more akin to the "this generator" and "this function" that have been proposed elsewhere.

Not bad at a glance. Have to check for ambiguities. Thanks,

# Peter Hall (18 years ago)

Or can't Self be used outside of the sorts of usage found in the proposal examples?

That's it.

In that case, I think it needs to be clearer about how the syntax can be used. Is it only for use as the "this" parameter for function types? Seems to me like it should be usable anywhere that the compiler can reasonably detect the type at compile-time.

e.g. // this usage is handy for boiler-plate code that you can just copy // and paste without modification, or from an IDE code snippet template class A { var a:Self; // or... // var a:this type; }

type B = {b:Self};

Other places where special treatment of the keyword would be useful is in interfaces, where the a function argument or return value should be the same as the type:

interface IClonable { public function clone():Self; } class A implements IClonable { public function clone():A { return new A() }; }

That is, users of the interface see the type as being the same as the interface, but implementors must use their own type here. The example is identical to clone's return type being IClonable, except that implementors are required to declare a stronger type.

Peter

# Brendan Eich (18 years ago)

On Aug 13, 2007, at 1:59 PM, Peter Hall wrote:

Or can't Self be used outside of the sorts of usage found in the proposal examples?

That's it.

In that case, I think it needs to be clearer about how the syntax can be used.

No doubt the proposal needs to be clearer to become a spec.

Is it only for use as the "this" parameter for function types?

No, but it is only for function types within object structural types.
See the "Use Cases" section:

 * clone, iterators: {f:function():Self}
 * binary methods, equals: { f : function(x:Self):boolean }
 * structural object methods: { f : function(this:Self, ...) } -  

only useful if structural objects with types

at proposals:self_type.

Note that the "same type" case, equals, requires a runtime check to
be generated. This eliminates the kind of instanceof or downcasting
hand-written boilerplate seen in Java's equals implementations.

Seems to me like it should be usable anywhere that the compiler can reasonably detect the type at compile-time.

e.g. // this usage is handy for boiler-plate code that you can just copy // and paste without modification, or from an IDE code snippet
template class A { var a:Self; // or... // var a:this type; }

This is not a strong motivation IMHO.

type B = {b:Self};

We don't allow recursive structural types in general, but this kind
of degenerate cycle might be ok. Cormac should comment.

Other places where special treatment of the keyword would be useful is in interfaces, where the a function argument or return value should be the same as the type:

interface IClonable { public function clone():Self; } class A implements IClonable { public function clone():A { return new A() }; }

Indeed -- those are use-cases for interfaces and classes as well as
for structural types. However, the proposal is focused on structural
types since unlike classes, |this| binding defaults to the dynamic
rule from ES1-3, which is weak.

That is, users of the interface see the type as being the same as the interface, but implementors must use their own type here. The example is identical to clone's return type being IClonable, except that implementors are required to declare a stronger type.

Good point. I'll defer to Cormac on commenting further.

# Eylon Stroh (18 years ago)

From the proposal: "The return type T2 is ok, even if the function returns a T1 instead... covariant occurrences of Self (eg in the result type) are replaced by T2"

Please correct me if I'm wrong, but it seems to me that return types can also be placed in contexts where covariance is not guaranteed. In the following (admittedly contrived) code snippet, the type of y.f cannot be function(this:T2):T2, or even function(this:):T2 (where * denotes run-time type checking). As far as I can tell, it has to be function(this:):*.

type T1 = { w:int, f:function(this:Self):Self }; var x:T1 = { w:5, f:function(this:T1):T1 { var result:T1; if(this.w < 1) { result = { w:1, f:this.f }:T1; } else { result = { w:this.w-1, f:this.f }:T1; // this requires f to return T1 result = this.f(result); result.w *= this.w; } return result; } }:T1;

type T2 = { f:function(this:Self):Self }; var y:T2 = x;

Have I missed something that allows for stronger typing of y.f?

Eylon

# Peter Hall (18 years ago)

On 8/14/07, Eylon Stroh <estroh at adobe.com> wrote:

From the proposal: "The return type T2 is ok, even if the function returns a T1 instead... covariant occurrences of Self (eg in the result type) are replaced by T2"

I was also wondering about this. To me, if someone writes "Self" for an argument or return type, they probably mean T1. If they actually want it to be "", why not let them write ""? Or is there something else going on here?

Peter

# Cormac Flanagan (18 years ago)

Peter Hall wrote:

Or can't Self be used outside of the sorts of usage found in the proposal examples? That's it. In that case, I think it needs to be clearer about how the syntax can be used. Is it only for use as the "this" parameter for function types? Seems to me like it should be usable anywhere that the compiler can reasonably detect the type at compile-time.

Yes, we plan to support Self in all reasonable places inside classes, interfaces, structural object types, and structural object expressions.

e.g. // this usage is handy for boiler-plate code that you can just copy // and paste without modification, or from an IDE code snippet template class A { var a:Self; // or... // var a:this type; }

type B = {b:Self};

Yes, I think this should be fine.

# Cormac Flanagan (18 years ago)

Eylon Stroh wrote:

From the proposal: "The return type T2 is ok, even if the function returns a T1 instead... covariant occurrences of Self (eg in the result type) are replaced by T2"

Please correct me if I'm wrong, but it seems to me that return types can also be placed in contexts where covariance is not guaranteed. In the following (admittedly contrived) code snippet, the type of y.f cannot be function(this:T2):T2, or even function(this:):T2 (where * denotes run-time type checking). As far as I can tell, it has to be function(this:):*.

type T1 = { w:int, f:function(this:Self):Self }; var x:T1 = { w:5, f:function(this:T1):T1 { var result:T1; if(this.w < 1) { result = { w:1, f:this.f }:T1; } else { result = { w:this.w-1, f:this.f }:T1; // this requires f to return T1 result = this.f(result); result.w *= this.w; } return result; } }:T1;

type T2 = { f:function(this:Self):Self }; var y:T2 = x;

Have I missed something that allows for stronger typing of y.f?

As far as I can see, we can give y.f the type

function(this:*):T2

since T1 is a subtype of T2.

# Cormac Flanagan (18 years ago)

Peter Hall wrote:

On 8/14/07, Eylon Stroh <estroh at adobe.com> wrote:

From the proposal: "The return type T2 is ok, even if the function returns a T1 instead... covariant occurrences of Self (eg in the result type) are replaced by T2"

I was also wondering about this. To me, if someone writes "Self" for an argument or return type, they probably mean T1. If they actually want it to be "", why not let them write ""? Or is there something else going on here?

Yes, it would be intuitive to replace "Self" by T1, but it would be unsound, since Self really means:

the actual allocated type of the current object, which is some unknown subtype of the known static type T1 of that object

so we can only safely replace Self by T1 in "covariant" contexts (where we get stuff out, and don't care if we actually get a subtype), but not in "contravariant" contexts, such as argument positions.

# liorean (18 years ago)

Peter Hall wrote:

type B = {b:Self};

On 14/08/07, Cormac Flanagan <cormac at soe.ucsc.edu> wrote:

Yes, I think this should be fine.

I'm all for allowing recursive structural types e.g. for use as binary trees or linked lists.

type BinTree = {sin:Self, dx:Self, value:*};
# Brendan Eich (18 years ago)

On Aug 14, 2007, at 10:10 AM, liorean wrote:

Peter Hall wrote:

type B = {b:Self};

On 14/08/07, Cormac Flanagan <cormac at soe.ucsc.edu> wrote:

Yes, I think this should be fine.

I'm all for allowing recursive structural types e.g. for use as binary trees or linked lists.

type BinTree = {sin:Self, dx:Self, value:*};

We've decided not to specify recursive structural types in general.
The subtype relation is O(exp(n)) for the simpler algorithm [1], and O (n^2) for a fairly complex successor algorithm [1]. In the interest
of simplicity and to minimize costs to small-device implementations,
we are leaving out recursive structural types of ES4. They could be
added later; for now, if you need trees and the like, use nominal types.

Dave Herman's analysis is on the wiki, but for some reason I'm
getting an error trying to access the relevant page [3].

Self-references may be good enough for common "recursive" cases
(iterator::get, clone, equals, etc.).

/be

[1] research.microsoft.com/Users/luca/Papers/SRT.pdf [2] www.cs.ucla.edu/~palsberg/paper/mscs95-kps.pdf [3] ECMA/wiki/doku.php? id=discussion:classes_as_structural_types_with_branding

# Nicolas Cannasse (18 years ago)

Brendan Eich a écrit :

On Aug 14, 2007, at 10:10 AM, liorean wrote:

Peter Hall wrote:

type B = {b:Self}; On 14/08/07, Cormac Flanagan <cormac at soe.ucsc.edu> wrote: Yes, I think this should be fine. I'm all for allowing recursive structural types e.g. for use as binary trees or linked lists.

type BinTree = {sin:Self, dx:Self, value:*};

We've decided not to specify recursive structural types in general.
The subtype relation is O(exp(n)) for the simpler algorithm [1], and O (n^2) for a fairly complex successor algorithm [1]. In the interest
of simplicity and to minimize costs to small-device implementations,
we are leaving out recursive structural types of ES4. They could be
added later; for now, if you need trees and the like, use nominal types.

That's too bad.

Have you not considered having a two-level type spec ? One for compile-time and one for runtime ? If I'm not wrong, Java did that with generics.

But anyway, haXe allows :

typedef BinTree<T> = { var value : T; var left : BinTree<T>; var right : BinTree<T>; }

Which can be generated as the former (less typed) BinTree definition while still getting proper compile-time typechecking.

The only problem so far is that it seems there is no structural types support in AVM2/Tamarin yet, or did I miss it ?

Best,

# Jeff Dyer (18 years ago)

On 8/14/07 12:46 PM, Nicolas Cannasse wrote:

The only problem so far is that it seems there is no structural types support in AVM2/Tamarin yet, or did I miss it ?

Nope, no support for structural types in AVM2.

# Brendan Eich (18 years ago)

On Aug 14, 2007, at 12:46 PM, Nicolas Cannasse wrote:

Have you not considered having a two-level type spec ? One for compile-time and one for runtime ? If I'm not wrong, Java did that
with generics.

We do not want a profiled or segmented specification, apart from the
optional strict mode; we do not want erasure. Since strict mode is
optional (browsers and small-device implementations will leave it
out), runtime semantics cannot depend on it.

The only problem so far is that it seems there is no structural types support in AVM2/Tamarin yet, or did I miss it ?

No, it's on the to-do list.

# Dave Herman (18 years ago)

Dave Herman's analysis is on the wiki, but for some reason I'm
getting an error trying to access the relevant page [3].

...

[3] ECMA/wiki/doku.php? id=discussion:classes_as_structural_types_with_branding

You've got the URL wrong here; it should be:

discussion:classes_as_structural_types_with_branding

Is there a broken link somewhere?

# Brendan Eich (18 years ago)

On Aug 16, 2007, at 11:42 AM, Dave Herman wrote:

Dave Herman's analysis is on the wiki, but for some reason I'm getting an error trying to access the relevant page [3].

...

[3] ECMA/wiki/doku.php? id=discussion:classes_as_structural_types_with_branding

You've got the URL wrong here; it should be:

doku.php? id=discussion:classes_as_structural_types_with_branding

Whoops, you're right -- mistranscription of the internal dokuwiki URL
to the new ecmascript.org one, sorry.

Is there a broken link somewhere?

No, but I'm getting permission denied now.

# Dave Herman (18 years ago)

Is there a broken link somewhere?

No, but I'm getting permission denied now.

You need to be logged in for the discussion: namespace.

# Peter Hall (18 years ago)

I think my confusion here, is perhaps that I'm seeing this a slightly different way. What you are saying seems to suggest that Self type annotations can behave differently at runtime. To me, this example from the wiki should be an error:

type T1 = { f:function(this:Self, z:Self):Self, w:int} var x:T1 = { ... }:T1 type T2 = { f:function(this:Self, z:Self):Self } var y:T2 = x; // ERROR

Because the types are equivalent to:

type T1 = { f:function(this:T1, z:T1):T1, w:int} type T2 = { f:function(this:T2, z:T2):T2}

So T1's f and T2's f are incompatible. In a real situation, an author would have to be a bit more careful about how the types are defined, and probably use wildcard types or Object (or perhaps fully expanded type definitions) where necessary. But I think that is better than having wildcards inserted automatically behind the scenes.

Peter

# Eylon Stroh (18 years ago)

Peter,

This was my confusion, as well. I was about to send a reply to Cormac's email but, in working through my reply, I realized what was going on.

The types are not quite equivalent to substituting T1 and T2, because Self is the concrete object type which needs to be a subtype of the defined type but not exactly the same, so the equivalent expressions are:

type T1 = { f:function(this:t1, z:t1):t1, w:int } for some t1 <: T1

type T2 = { f:function(this:t2, z:t2):t2 } for some t2 <: T2

This means that

var x:T1 = { ... }:T1; var y:T2 = x;

is not an error, because T1 is a legitimate candidate for t2 <: T2.

-- Eylon --