notating "any subtype of self type"

44 views
Skip to first unread message

Dirk Lattermann

unread,
May 29, 2015, 4:05:33 AM5/29/15
to ceylon...@googlegroups.com
Hello,

still trying to get used to self types.

Is there a way to notate "any subtype of an abstract class with self
type"?

Maybe there's a totally different better way to achieve the following
(which does not compile)? Do you have suggestions?

Thanks, Dirk.

------------------------
abstract class Base<Self>() of Self
given Self satisfies Base<Self>
{shared variable Integer a = 0;}

class Sub1() extends Base<Sub1>(){shared variable Integer b = 0;}

class Sub2() extends Base<Sub2>(){shared variable Integer b = 0;}

Sub1 | Sub2 deserialize(String s)
{
switch (s)
case ("sub1") {
return Sub1();
}
case ("sub2") {
return Sub2();
}
else {
throw Exception("invalid input data");
}
}

void process<X>(X input)
given X satisfies Base<X>
{
switch (input)
case (is Sub1) {
// process Sub1
}
case (is Sub2) {
// process Sub2
}
else {
throw Exception("unsupported input class");
}
}

void run()
{
String sx = "sub2";

// rather no reference to subtype desired here
// how to write Base<any subtype>?
Sub1 | Sub2 inputX = deserialize(sx);

// some generic processing on Base
print(inputX.a.string); // error: no access to a?

// process depending on subtype
process(inputX); // error
}
---------------------

Gavin King

unread,
May 29, 2015, 7:52:27 AM5/29/15
to ceylon...@googlegroups.com
You mean you want to be able to write stuff like:

Numeric<out Anything>

To say: anything that satisfies any instantiation of Numeric?

But Ceylon rejects that because Anything doesn't satisfy the self type constraint?

Sadly there is no way right now because the Java backend doesn't support it. There's no problem from my (typechecker) point of view, and there is an open issue to remove that restriction.

For now you have to write a generic method parameterized over the self type and write Comparable<T> where you have a constraint T satisfies Comparable<T>.

It's irritating but it works.

Sent from my iPhone
> --
> You received this message because you are subscribed to the Google Groups "ceylon-users" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to ceylon-users...@googlegroups.com.
> To post to this group, send email to ceylon...@googlegroups.com.
> Visit this group at http://groups.google.com/group/ceylon-users.
> To view this discussion on the web visit https://groups.google.com/d/msgid/ceylon-users/20150529100531.6aa8ee44%40PC192-168-37-106.Speedport_W_724V_Typ_A_05011602_00_001.
> For more options, visit https://groups.google.com/d/optout.

Dirk Lattermann

unread,
May 29, 2015, 9:11:13 AM5/29/15
to ceylon...@googlegroups.com
Am Fri, 29 May 2015 13:52:19 +0200
schrieb Gavin King <gavin...@gmail.com>:

> You mean you want to be able to write stuff like:
>
> Numeric<out Anything>
>
> To say: anything that satisfies any instantiation of Numeric?
>

Well, probably yes, without knowing the spec's wording for it, I'd say
"anything that satisfies the Base<T> class, regardless of the type
parameter T for Base<T>".

> Sadly there is no way right now because the Java backend doesn't
> support it. There's no problem from my (typechecker) point of view,
> and there is an open issue to remove that restriction.
>
> For now you have to write a generic method parameterized over the
> self type and write Comparable<T> where you have a constraint T
> satisfies Comparable<T>.
>
> It's irritating but it works.

I'm irritated :-) -- did you describe a workaround like I was trying
to find in function process() in my example? If I want to use such a
method (process() in my example), I either need to specify the type
parameter or have an attribute with a concrete type as parameter, but
the point is writing the subtype independent stuff without knowing the
exact subtype.

Could I call a method like you describe in your workaround directly
from a "void run()" method?

The other thing that puzzles me is the apparent inability to access
Base class attributes from the union type Sub1 | Sub2.

Gavin King

unread,
May 29, 2015, 9:15:43 AM5/29/15
to ceylon...@googlegroups.com
On Fri, May 29, 2015 at 3:11 PM, Dirk Lattermann <dl...@alqualonde.de> wrote:

> Well, probably yes, without knowing the spec's wording for it, I'd say
> "anything that satisfies the Base<T> class, regardless of the type
> parameter T for Base<T>".

Yeah, right, so you would write that as Base<out Anything> or
alternatively Base<in Nothing>, i.e. Base<T> for some T that is a
subtype of Anything, or, alternatively, Base<T> for some T that is a
supertype of Nothing.




--
Gavin King
ga...@ceylon-lang.org
http://profiles.google.com/gavin.king
http://ceylon-lang.org
http://hibernate.org
http://seamframework.org

Gavin King

unread,
May 29, 2015, 9:19:03 AM5/29/15
to ceylon...@googlegroups.com
On Fri, May 29, 2015 at 3:11 PM, Dirk Lattermann <dl...@alqualonde.de> wrote:

> The other thing that puzzles me is the apparent inability to access
> Base class attributes from the union type Sub1 | Sub2.

I don't know what you mean: that code works for me, and it _should_
work (in all versions of Ceylon). To be clear, the line:

print(inputX.a.string);

Passes the typechecker, and prints "0" when I run it.

Gavin King

unread,
May 29, 2015, 9:41:12 AM5/29/15
to ceylon...@googlegroups.com
On Fri, May 29, 2015 at 3:11 PM, Dirk Lattermann <dl...@alqualonde.de> wrote:

> I'm irritated :-) -- did you describe a workaround like I was trying
> to find in function process() in my example? If I want to use such a
> method (process() in my example), I either need to specify the type
> parameter or have an attribute with a concrete type as parameter, but
> the point is writing the subtype independent stuff without knowing the
> exact subtype.

Yes, that's sorta what I mean, except your process() is a bad example
of it since it doesn't actually use the constraint, it uses a switch
instead!

What's going wrong in your case is you have a Sub1 | Sub2 which is not
a valid X, since Sub1 | Sub2 is not a subtype of Base<Sub1 | Sub2>.

There's a couple of possible workarounds here:

1. Make Base<Self> covariant in Self. With the addition of a single
"out" annotation, your code compiles and runs correctly:

abstract class Base<out Self>() of Self
given Self satisfies Base<Self> { ... }

2. Use a switch on the calling side:

switch (inputX)
case (is Sub1) { process(inputX); }
case (is Sub2) { process(inputX); }

3. Use Base<in Nothing> as the parameter type to process(). This is I
guess the best answer to your question, at least for now.

Gavin King

unread,
May 29, 2015, 9:50:25 AM5/29/15
to ceylon...@googlegroups.com
To be clear, the compiler currently accepts stuff like this:

Numeric<in Nothing> num = 1;

Even though it doesn't accept:

Numeric<out Anything> num = 1;

It should accept both, of course, and the only reason for this
limitation was some JVM backend implementation concerns. Please see
the following issue:

https://github.com/ceylon/ceylon-spec/issues/535

John Vasileff

unread,
May 29, 2015, 9:51:29 AM5/29/15
to ceylon...@googlegroups.com
Weird. I get the error with a union of an *invariant* generic type on Ceylon v1.1.0. Looks like support of this has since been added.

class Box<T>(shared T a) {}
void testing() {
Box<String> | Box<Integer> box = Box(1);
print(box.a.string);
// Method or attribute does not exist: a in type Box<String>|Box<Integer>
}

John
> --
> You received this message because you are subscribed to the Google Groups "ceylon-users" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to ceylon-users...@googlegroups.com.
> To post to this group, send email to ceylon...@googlegroups.com.
> Visit this group at http://groups.google.com/group/ceylon-users.
> To view this discussion on the web visit https://groups.google.com/d/msgid/ceylon-users/CAP7PoCfFZecNOn2zP-V7fZoYH_2nYCHb%2B9CS04oH_AHkBxr_Pw%40mail.gmail.com.

Gavin King

unread,
May 29, 2015, 10:09:33 AM5/29/15
to ceylon...@googlegroups.com
Aaaaah. Yeah, sure, I forgot there used to be a restriction for the
invariant case. Now I use use-site variance to form the principal
instantiation. So yeah, OK, I was wrong, that *was* a limitation in
1.1.
> To view this discussion on the web visit https://groups.google.com/d/msgid/ceylon-users/124F4952-6C01-49BB-8074-6E9DD3F1C37A%40vasileff.com.
> For more options, visit https://groups.google.com/d/optout.



Dirk Lattermann

unread,
May 29, 2015, 11:00:37 AM5/29/15
to ceylon...@googlegroups.com
Am Fri, 29 May 2015 16:09:12 +0200
schrieb Gavin King <gavin...@gmail.com>:

I removed the union type now and added Base<out Self> to the definition
of Base. Now I can't use use-site variance and have a working process()
function, but a compilation error in deserialize().
If I write Base<in Self>, then the deserialize() works, but the
process() yields a compilation error.

Should I try use-site variance with an newer ceylon version than 1.1?
I haven't switched to development builds as I want to use the IDE. Is
the development IDE built with recent versions of ceylon?

I now have the following:

----------------------
abstract class Base<out Self>() of Self
given Self satisfies Base<Self>
{shared variable Integer a = 0;}

class Sub1() extends Base<Sub1>(){shared variable Integer b = 0;}

class Sub2() extends Base<Sub2>(){shared variable Integer b = 0;}

Base<Nothing> deserialize(String s)
{
switch (s)
case ("sub1") {
Sub1 r = Sub1();
return r; // error if class Base<out Self>, "in" works
}
case ("sub2") {
Sub2 r = Sub2();
return r; // error if class Base<out Self>, "in" works
}
else {
throw Exception("invalid input data");
}
}

void process<X>(X input)
given X satisfies Base<X>
{
switch (input)
case (is Sub1) {
// process Sub1
}
case (is Sub2) {
// process Sub2
}
else {
throw Exception("unsupported input class");
}
}

void run()
{
String sx = "sub2";
Base<Nothing> inputX = deserialize(sx);

// some generic processing on Base
print(inputX.a.string);

process(inputX); // erorr if class Base<in Self>, "out" works
}
----------------------

Thanks, Dirk

Gavin King

unread,
May 29, 2015, 11:51:46 AM5/29/15
to ceylon...@googlegroups.com
So this worked for me:

abstract class Base<Self>() of Self
given Self satisfies Base<Self>
{shared variable Integer a = 0;}

class Sub1() extends Base<Sub1>(){shared variable Integer b = 0;}

class Sub2() extends Base<Sub2>(){shared variable Integer b = 0;}

Base<in Nothing> deserialize(String s)
{
switch (s)
case ("sub1") {
Sub1 r = Sub1();
return r; // error if class Base<out Self>, "in" works
}
case ("sub2") {
Sub2 r = Sub2();
return r; // error if class Base<out Self>, "in" works
}
else {
throw Exception("invalid input data");
}
}

void process(Base<in Nothing> input) {
switch (input)
case (is Sub1) {
// process Sub1
}
case (is Sub2) {
// process Sub2
}
else {
throw Exception("unsupported input class");
}
}

shared void runit()
{
String sx = "sub2";
Base<in Nothing> inputX = deserialize(sx);
> To view this discussion on the web visit https://groups.google.com/d/msgid/ceylon-users/20150529170034.5561209f%40PC192-168-37-106.Speedport_W_724V_Typ_A_05011602_00_001.
> For more options, visit https://groups.google.com/d/optout.



--

Gavin King

unread,
May 29, 2015, 11:54:08 AM5/29/15
to ceylon...@googlegroups.com
This also worked:


abstract class Base<out Self>() of Self
given Self satisfies Base<Self>
{shared variable Integer a = 0;}

class Sub1() extends Base<Sub1>(){shared variable Integer b = 0;}

class Sub2() extends Base<Sub2>(){shared variable Integer b = 0;}

Base<Sub1|Sub2> deserialize(String s)
{
switch (s)
case ("sub1") {
Sub1 r = Sub1();
return r; // error if class Base<out Self>, "in" works
}
case ("sub2") {
Sub2 r = Sub2();
return r; // error if class Base<out Self>, "in" works
}
else {
throw Exception("invalid input data");
}
}

void process(Base<Sub1|Sub2> input) {
switch (input)
case (is Sub1) {
// process Sub1
}
case (is Sub2) {
// process Sub2
}
else {
throw Exception("unsupported input class");
}
}

shared void runit()
{
String sx = "sub2";
Base<Sub1|Sub2> inputX = deserialize(sx);

Dirk Lattermann

unread,
May 29, 2015, 12:35:22 PM5/29/15
to ceylon...@googlegroups.com
Thank you very much for your suggestions!

The solution with union type fits for the deserialize and process
functions to denote which subtypes they support. However, it'd be
nicer if the "generic" part calling function could stay unmodified if
another subtype is introduced. Therefore, I prefer the use-site
variance solution.

I thought use-site variance was introduced for Java interoperability.
Is this maybe a legitimate use case for it?

Dirk

Am Fri, 29 May 2015 17:53:46 +0200

Gavin King

unread,
May 29, 2015, 2:01:45 PM5/29/15
to ceylon...@googlegroups.com
Dirk, this discussion inspired me to go and see what would happen if I
relaxed the typechecker constraint that is causing us grief here, and,
what do you know, the backend tolerated this! (I guess it was a
side-effect of the work done to support use-site variance.)

So it looks like I'll be able to close this issue, once I update the spec:

https://github.com/ceylon/ceylon-spec/issues/535
> To view this discussion on the web visit https://groups.google.com/d/msgid/ceylon-users/20150529183518.4e2e2451%40PC192-168-37-106.Speedport_W_724V_Typ_A_05011602_00_001.

Dirk Lattermann

unread,
May 30, 2015, 9:51:12 AM5/30/15
to ceylon...@googlegroups.com
Am Fri, 29 May 2015 20:01:24 +0200
schrieb Gavin King <gavin...@gmail.com>:

> Dirk, this discussion inspired me to go and see what would happen if I
> relaxed the typechecker constraint that is causing us grief here, and,
> what do you know, the backend tolerated this! (I guess it was a
> side-effect of the work done to support use-site variance.)
>
> So it looks like I'll be able to close this issue, once I update the
> spec:
>
> https://github.com/ceylon/ceylon-spec/issues/535

nice!
Reply all
Reply to author
Forward
0 new messages