Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

make "annotation constructors" actual constructors #1428

Open
gavinking opened this issue Sep 27, 2015 · 19 comments
Open

make "annotation constructors" actual constructors #1428

gavinking opened this issue Sep 27, 2015 · 19 comments
Assignees
Milestone

Comments

@gavinking
Copy link
Member

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 gavinking self-assigned this Sep 27, 2015
@gavinking gavinking added this to the 1.3 milestone Sep 27, 2015
@gavinking
Copy link
Member Author

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
Copy link
Member Author

Then this:

object foo {}

would just mean this:

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

Which is just what I want, frankly.

@quintesse
Copy link
Member

More generic perhaps would be outer?

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

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

@gavinking
Copy link
Member Author

@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
Copy link
Member

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
Copy link
Member Author

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:

  • decide precisely what are the semantics for shared for these things, and implement that,
  • let them be annotation constructors.

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
Copy link
Member Author

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
Copy link
Member Author

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
Copy link
Member Author

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

And block is probably out for much the same reason.

@gavinking
Copy link
Member Author

We could use out, perhaps...

@gavinking
Copy link
Member Author

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

@gavinking
Copy link
Member Author

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
Copy link
Member

Would this also be allowed?

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

@gavinking
Copy link
Member Author

@lucaswerkmeister well currently we don't support this:

import pack { C { o { s } } }

So, no.

@ghost
Copy link

ghost commented Sep 30, 2015

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
Copy link
Member Author

Shouldn't classes with constructors not have parameter lists?

Yes of course. Copy/paste error.

@gavinking
Copy link
Member Author

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;

@ghost
Copy link

ghost commented Oct 17, 2015

@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
Copy link
Member Author

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants