Subtypes of typedefs

classic Classic list List threaded Threaded
7 messages Options
Reply | Threaded
Open this post in threaded view
|

Subtypes of typedefs

Jordo Odroj
assuming we have (minus any typos) 
typedef twoFields {
   i: int;
   j: int;
}
typedef threeFields {
  i: int;
  j: int;
  k: int;
}


if I have a method that declares that it takes twoFields, why can't I pass it an instance of threeFields?
function x(tf: twoFields) {
   trace(twoFields.i);
}
I should be able to say:
x(someInstanceOfThreeFields);

I'm suppose that what I'm asking for is some kind of .. *static* duck typing. (Did the universe just explode by saying that?)

Does anyone know a workaround? Would people be supportive of me making this a language feature?

--
haXe - an open source web programming language
http://haxe.org
Reply | Threaded
Open this post in threaded view
|

Re: Subtypes of typedefs

Nicolas Cannasse
Le 02/02/2011 12:40, Jordo Odroj a écrit :

> assuming we have (minus any typos)
> typedef twoFields {
>     i: int;
>     j: int;
> }
> typedef threeFields {
>    i: int;
>    j: int;
>    k: int;
> }
>
>
> if I have a method that declares that it takes twoFields, why can't I
> pass it an instance of threeFields?
> function x(tf: twoFields) {
>     trace(twoFields.i);
> }
> I should be able to say:
> x(someInstanceOfThreeFields);

It works, but the compiler will complain is you pass it a constant
object that has not been typed as threeFields.

example :

var o = { i : 0, j : 0, k : 0 };
x(o); // error

var o : threeFields = ....;
x(o); // ok

This is not very intuitive, but it's meant to prevent having a lot of
unused declared fields when for example removing a field from a typedef.

Nicolas

--
haXe - an open source web programming language
http://haxe.org
Reply | Threaded
Open this post in threaded view
|

Re: Subtypes of typedefs

Jordo Odroj
I run into another problem that doesn't seem to have an easy solution:
on top of what's already been stated assume:
typedef container = {
   var tf: twoFields;
}

var threefieldsinstance : threeFields = {
.. the three fields here
}
var x: container = {
    tf: threefieldsinstance    // this doesn't work
}
Even if threefieldsinstance was declared to be an instance of threefields, it won't work.
According to your last post, I would have expected this to work, because I've explicity stated the type.
Furthermore, even if this did work, there is still no way for me to "inline" a threefields instance because I have no way to specify a type when placing json style object leterals.
This restriction seems to be pretty limiting, with only a slight gain (as you mentioned). If the restriction were lifted (the one that you described as being intentional) noone's code would break, but rather code in the future would be more permissive. 

On Wed, Feb 2, 2011 at 3:44 AM, Nicolas Cannasse <[hidden email]> wrote:
Le 02/02/2011 12:40, Jordo Odroj a écrit :

assuming we have (minus any typos)
typedef twoFields {
   i: int;
   j: int;
}
typedef threeFields {
  i: int;
  j: int;
  k: int;
}


if I have a method that declares that it takes twoFields, why can't I
pass it an instance of threeFields?
function x(tf: twoFields) {
   trace(twoFields.i);
}
I should be able to say:
x(someInstanceOfThreeFields);

It works, but the compiler will complain is you pass it a constant object that has not been typed as threeFields.

example :

var o = { i : 0, j : 0, k : 0 };
x(o); // error

var o : threeFields = ....;
x(o); // ok

This is not very intuitive, but it's meant to prevent having a lot of unused declared fields when for example removing a field from a typedef.

Nicolas

--
haXe - an open source web programming language
http://haxe.org


--
haXe - an open source web programming language
http://haxe.org
Reply | Threaded
Open this post in threaded view
|

Re: Subtypes of typedefs

Jordo Odroj
Nicolas (or anyone) do you know why this example that I cited doesn't compile? It seems much like the function example that Nicolas provided as a workaround.
Ideally, I'd like be able to always have an object literal be a subtype of any other typedef that contains a subset of the fields (recursively of course) is there an existing part of the compler types. I seems like win from allowing this is greater than the loss from restricting it. It could be that I don't fully understand the motivation, however.


On Wed, Feb 2, 2011 at 3:41 PM, Jordo Odroj <[hidden email]> wrote:
I run into another problem that doesn't seem to have an easy solution:
on top of what's already been stated assume:
typedef container = {
   var tf: twoFields;
}

var threefieldsinstance : threeFields = {
.. the three fields here
}
var x: container = {
    tf: threefieldsinstance    // this doesn't work
}
Even if threefieldsinstance was declared to be an instance of threefields, it won't work.
According to your last post, I would have expected this to work, because I've explicity stated the type.
Furthermore, even if this did work, there is still no way for me to "inline" a threefields instance because I have no way to specify a type when placing json style object leterals.
This restriction seems to be pretty limiting, with only a slight gain (as you mentioned). If the restriction were lifted (the one that you described as being intentional) noone's code would break, but rather code in the future would be more permissive. 

On Wed, Feb 2, 2011 at 3:44 AM, Nicolas Cannasse <[hidden email]> wrote:
Le 02/02/2011 12:40, Jordo Odroj a écrit :

assuming we have (minus any typos)
typedef twoFields {
   i: int;
   j: int;
}
typedef threeFields {
  i: int;
  j: int;
  k: int;
}


if I have a method that declares that it takes twoFields, why can't I
pass it an instance of threeFields?
function x(tf: twoFields) {
   trace(twoFields.i);
}
I should be able to say:
x(someInstanceOfThreeFields);

It works, but the compiler will complain is you pass it a constant object that has not been typed as threeFields.

example :

var o = { i : 0, j : 0, k : 0 };
x(o); // error

var o : threeFields = ....;
x(o); // ok

This is not very intuitive, but it's meant to prevent having a lot of unused declared fields when for example removing a field from a typedef.

Nicolas

--
haXe - an open source web programming language
http://haxe.org



--
haXe - an open source web programming language
http://haxe.org
Reply | Threaded
Open this post in threaded view
|

Re: Subtypes of typedefs

Nicolas Cannasse
In reply to this post by Jordo Odroj
Le 03/02/2011 00:41, Jordo Odroj a écrit :

> I run into another problem that doesn't seem to have an easy solution:
> on top of what's already been stated assume:
> typedef container = {
>     var tf: twoFields;
> }
>
> var threefieldsinstance : threeFields = {
> .. the three fields here
> }
> var x: container = {
>      tf: threefieldsinstance    // this doesn't work
> }
> Even if threefieldsinstance was declared to be an instance of
> threefields, it won't work.


This is normal, because let's say that the compiler allow it :

typedef container = {
     var tf: twoFields;
}
typedef container3 = {
     var tf: threeFields;
}

var x : container3 = { var tf : threeFieldsInstance };
var c : container = x;
c.tf = twoFieldInstance; // would break a container3 !

So the compiler allow allow such subtyping in case of read-only fields.

Nicolas

--
haXe - an open source web programming language
http://haxe.org
Reply | Threaded
Open this post in threaded view
|

Re: Subtypes of typedefs

Jordo Odroj
Nicolas, I think it will help us to think about the subtyping rules of references to containers, and the subtyping of what the containers can *contain* as being two completely separate things. This is how haxe treats it's Generics template classes. Haxe is awesome, in that it's generics implement "Invariant Subtyping" (as I'm sure you know.) In summary (for those who don't know) Invariant subtyping means that you can assign an ArrayList<String> to a variable that is declared as List<String> but you can *not* assign a List<ChildString> to a List<String> (for exactly the same reason you pointed out, Nicolas)

In fact what I'm desiring, is not to do as you mentioned (which was to allow assigning a container3 to a container reference), but rather to have the object literals carry the exact same paradigm as haxe generics.

I think that object literals are one of Haxe's distinguishing features, and they're awesome. I'd love to start using them in many parts of my code.

But there are two questions here. 
Should threeFields be a subtype of Twofields?
Should container3 be a subtype of container?
(x subtype of y means x can b used anywhere a y can be used). 

I think the answer will be absolutely clear if we transform the problem into one that we understand very well, and that haxe gets *absolutely* right (awesome job Nicolas, et all).

If we have
typedef Parent = {
   var i : Int;
}
typedef Child = {
    var i: Int;
    var j: int;
}
Clearly Child is a subtype of Parent. I believe haxe gets this right (although you need to explicitly declare the type of a Child instance for it to work).

There are two possible containers we could have. I won't even try to relate them as children/parents of each other because it may not be the case, as we will soon discover. I'll call them Red and Black, without loss of generality.

typedef Red = {
    var thingReference: Parent;
}
typedef Black = {
    var thingReference: Child;
}
Red just happens to contain a parent reference and Black just happens to contain a Child reference.
Your example (accurately) showed why Black is not a subtype of Red. For the exact same reason as why as List<StringSubclass> is not a subtype of List<String>.
"A container that holds more 'capable' elements, is not a subtype of a container that contains less 'capable' elements, because of the mutation problem that you mentioned"
However, why can't I create a Red who's "thingReference" points to a child reference? Much like you can have a List<String> that contains an instance of SubString.
In fact, I think it would help to think of:
typedef Red = Container<Parent>;
typedef Black = Container<Child>;

In haxe, you can say:
var parentContainer = new Container<Parent>();
parentContainer.hold(child);

I'm thinking that object literals should obey the same principles of invariant subtyping.
I think the *easiest* way to do this, is either in a macro (and hopefully eventually the compiler) by transforming typedefs into generics behind the scenes.
The harder (but probably better way TBPH) would be to actually just do the same analysis on object literals/typedefs that we do on generics.

Jordohx


On Sat, Feb 5, 2011 at 8:58 AM, Nicolas Cannasse <[hidden email]> wrote:
Le 03/02/2011 00:41, Jordo Odroj a écrit :

I run into another problem that doesn't seem to have an easy solution:
on top of what's already been stated assume:
typedef container = {
   var tf: twoFields;
}

var threefieldsinstance : threeFields = {
.. the three fields here
}
var x: container = {
    tf: threefieldsinstance    // this doesn't work
}
Even if threefieldsinstance was declared to be an instance of
threefields, it won't work.


This is normal, because let's say that the compiler allow it :


typedef container = {
   var tf: twoFields;
}
typedef container3 = {
   var tf: threeFields;
}

var x : container3 = { var tf : threeFieldsInstance };
var c : container = x;
c.tf = twoFieldInstance; // would break a container3 !

So the compiler allow allow such subtyping in case of read-only fields.


Nicolas

--
haXe - an open source web programming language
http://haxe.org


--
haXe - an open source web programming language
http://haxe.org
Reply | Threaded
Open this post in threaded view
|

Re: Subtypes of typedefs

Jordo Odroj
So what do you think Nicolas? I've been looking through the the compiler source code. It seems like this is something that needs to be done at the process of generating the AST (am I correct?) Or is there an intermediate step after the AST is generated, where we analyze advanced subtypes? (But before code gen).


On Sat, Feb 5, 2011 at 6:20 PM, Jordo Odroj <[hidden email]> wrote:
Nicolas, I think it will help us to think about the subtyping rules of references to containers, and the subtyping of what the containers can *contain* as being two completely separate things. This is how haxe treats it's Generics template classes. Haxe is awesome, in that it's generics implement "Invariant Subtyping" (as I'm sure you know.) In summary (for those who don't know) Invariant subtyping means that you can assign an ArrayList<String> to a variable that is declared as List<String> but you can *not* assign a List<ChildString> to a List<String> (for exactly the same reason you pointed out, Nicolas)

In fact what I'm desiring, is not to do as you mentioned (which was to allow assigning a container3 to a container reference), but rather to have the object literals carry the exact same paradigm as haxe generics.

I think that object literals are one of Haxe's distinguishing features, and they're awesome. I'd love to start using them in many parts of my code.

But there are two questions here. 
Should threeFields be a subtype of Twofields?
Should container3 be a subtype of container?
(x subtype of y means x can b used anywhere a y can be used). 

I think the answer will be absolutely clear if we transform the problem into one that we understand very well, and that haxe gets *absolutely* right (awesome job Nicolas, et all).

If we have
typedef Parent = {
   var i : Int;
}
typedef Child = {
    var i: Int;
    var j: int;
}
Clearly Child is a subtype of Parent. I believe haxe gets this right (although you need to explicitly declare the type of a Child instance for it to work).

There are two possible containers we could have. I won't even try to relate them as children/parents of each other because it may not be the case, as we will soon discover. I'll call them Red and Black, without loss of generality.

typedef Red = {
    var thingReference: Parent;
}
typedef Black = {
    var thingReference: Child;
}
Red just happens to contain a parent reference and Black just happens to contain a Child reference.
Your example (accurately) showed why Black is not a subtype of Red. For the exact same reason as why as List<StringSubclass> is not a subtype of List<String>.
"A container that holds more 'capable' elements, is not a subtype of a container that contains less 'capable' elements, because of the mutation problem that you mentioned"
However, why can't I create a Red who's "thingReference" points to a child reference? Much like you can have a List<String> that contains an instance of SubString.
In fact, I think it would help to think of:
typedef Red = Container<Parent>;
typedef Black = Container<Child>;

In haxe, you can say:
var parentContainer = new Container<Parent>();
parentContainer.hold(child);

I'm thinking that object literals should obey the same principles of invariant subtyping.
I think the *easiest* way to do this, is either in a macro (and hopefully eventually the compiler) by transforming typedefs into generics behind the scenes.
The harder (but probably better way TBPH) would be to actually just do the same analysis on object literals/typedefs that we do on generics.

Jordohx


On Sat, Feb 5, 2011 at 8:58 AM, Nicolas Cannasse <[hidden email]> wrote:
Le 03/02/2011 00:41, Jordo Odroj a écrit :

I run into another problem that doesn't seem to have an easy solution:
on top of what's already been stated assume:
typedef container = {
   var tf: twoFields;
}

var threefieldsinstance : threeFields = {
.. the three fields here
}
var x: container = {
    tf: threefieldsinstance    // this doesn't work
}
Even if threefieldsinstance was declared to be an instance of
threefields, it won't work.


This is normal, because let's say that the compiler allow it :


typedef container = {
   var tf: twoFields;
}
typedef container3 = {
   var tf: threeFields;
}

var x : container3 = { var tf : threeFieldsInstance };
var c : container = x;
c.tf = twoFieldInstance; // would break a container3 !

So the compiler allow allow such subtyping in case of read-only fields.


Nicolas

--
haXe - an open source web programming language
http://haxe.org



--
haXe - an open source web programming language
http://haxe.org