ceylon / ceylon-spec

DEPRECATED
Apache License 2.0
108 stars 34 forks source link

make "annotation constructors" actual constructors #1428

Open gavinking opened 8 years ago

gavinking commented 8 years ago

Now that we have the notion of a constructor, and they happen to have lowercase names, it would make more sense if annotation constructors were constructors.

shared final annotation class Doc {
    shared String text;
    shared new doc(String text) {
        this.text = text;
    }
}

The only thing that doesn't quite fit here is that it looks like you would have to import the doc annotation like this:

import ceylon.language { Doc { doc } }

That would be quite inconvenient, and not backward compatible. So we would need to have a special rule that says you can import an annotation constructor like this:

import ceylon.language { doc }

I don't think that's too offensive a notion.

gavinking commented 8 years ago

So we would need to have a special rule

This is actually a recurring theme. For example, the definition of the syntax object foo {} is this:

final class \Ifoo {
    shared new foo {}
}
\Ifoo  foo => \Ifoo.foo;  //yew!

Similarly, I would love to rewrite Boolean like this:

shared final class Boolean
        of true | false {
    shared actual String string;
    shared new true { string="true"; }
    shared new false { string="false"; } 
}

And Null/null like this:

shared final class Null
        of null 
        extends Anything() {
    shared new null {}
}

In both cases, and in the case of the annotation constructor proposal above, I run into the problem that a constructor isn't a toplevel. Hrm, what if there were a syntax like this:

shared final class Boolean of true | false {
    shared actual String string;
    shared new package.true { string="true"; }
    shared new package.false { string="false"; } 
}

That would kill three birds with one stone.

gavinking commented 8 years ago

Then this:

object foo {}

would just mean this:

final class \Ifoo {
    shared new package.foo {}
}

Which is just what I want, frankly.

quintesse commented 8 years ago

More generic perhaps would be outer?

final class \Ifoo {
    shared new outer.foo {}
}

Because object foo {} need not be a toplevel, right?

gavinking commented 8 years ago

@quintesse hrm I suppose you're right. Except outer can never refer to a package. Don't tell me we need to allow both...

quintesse commented 8 years ago

Well perhaps not, I guess you could just say: "it would be like writing blah blah package blah blah" for a toplevel object and "blah blah outer blah blah" for member and local objects.

It's not like you'd ever want to use that syntax over the normal object syntax anyway... right?

gavinking commented 8 years ago

So I implemented support for the new package.constructor syntax in the typechecker—really trivial, thought it surely could have bugs—and, believe it or not, it's already working for callable constructors on the JVM. (But not for value constructors, and not on the JS backend.)

Still to do:

I find the syntax pretty natural and convenient. And it really does the work of nailing down what object means which is very nice.

gavinking commented 8 years ago

And it really does the work of nailing down what object means which is very nice.

Well, OK, wait, let me walk that back a little. We can have nested objects and local objects. The package qualifier nails down the semantics of toplevel objects, which are the most important, since they can be switched. Adding an outer qualifier would achieve the same for nested objects.

But local objects remain an outlier: we currently have no keyword that identifies the immediately-containing scope. (outer always refers to the containing class/interface.) Now, a keyword like that would be nice anyway, but it doesn't exist right now.

gavinking commented 8 years ago

Now, a keyword like that would be nice anyway

A couple of options: body, block .... or even just function.

Using function within the body of a constructor or getter/setter would be pretty weird, but since I think it's essentially never going to be used explicitly in those contexts in practice, it might be OK.

gavinking commented 8 years ago

Ah: body sucks because it would hammer Html { head = ... ; body = ... ; }.

And block is probably out for much the same reason.

gavinking commented 8 years ago

We could use out, perhaps...

gavinking commented 8 years ago

Or, the other obvious option is to just use an annotation: promoted, possibly.

gavinking commented 8 years ago

Actually there is a use for this that has nothing to do with constructors, I suppose. According to §4.2.2, we can also import members of toplevel objects.

It might be nice if you could write:

import ceylon.language { milliseconds }

As an alternative to:

import ceylon.language { system { milliseconds } }

Just by annotating system.milliseconds with whatever we choose: package. or promoted or whatever.

lucaswerkmeister commented 8 years ago

Would this also be allowed?

shared class C() {
    shared object o {
        shared promoted String s => "s";
    }
}
shared void run() => print(C().s);
gavinking commented 8 years ago

@lucaswerkmeister well currently we don't support this:

import pack { C { o { s } } }

So, no.

zamfofex commented 8 years ago

Not that I'm agreeing with this whole constructor thingie, but since they're not going away (:sob:), I think constructors should simply naturally be accessible from the same scope as its class... Also, @gavinking, I'm confused by your Boolean and Null example thingies... Shouldn't classes with constructors not have parameter lists?

gavinking commented 8 years ago

Shouldn't classes with constructors not have parameter lists?

Yes of course. Copy/paste error.

gavinking commented 8 years ago

An good alternative to promoted, that is of more general use, would be to introduce a streamlined syntax for class and function aliases for the case where you're not changing the type or parameter list. For example:

function joinWithCommas => ", ".join;

And:

class Strings => Array<String>;

Which we currently force you to write using a much more verbose syntax, for example:

function joinWithCommas({Object*} objects) => ", ".join(objects);

Then, we could rewrite my annotation example above like this:

shared final annotation class Doc {
    shared String text;
    shared new doc(String text) {
        this.text = text;
    }
}
shared function doc => Doc.doc;

Or, more simply, as:

shared final annotation class Doc(text) {
    shared String text;
}
shared function doc => Doc;

But Boolean would not be improved as much:

shared final class Boolean
        of true | false {
    shared actual String string;
    shared new true { string="true"; }
    shared new false { string="false"; } 
}

shared value true = Boolean.true;
shared value false = Boolean.false;
zamfofex commented 8 years ago

@gavinking

Well, currently you can do that:

value joinWithCommas => ", ".join;

and:

function joinWithCommas({Object*} objs);
joinWithCommas = ", ".join;

The first is cleaner, but the last allows you to override methods.

I really like the idea of the simplified class aliases, but honestly I feel like the function aliases looks a lot like value declarations with a small semantic difference that may makes sense implementation-wise on the jvm, but conceptually, it just feels like a clunky, and unnecessary difference...

gavinking commented 8 years ago

But it's not quite the same: you lose the parameter names, which are very important in this case.