Skip to content
This repository has been archived by the owner on Apr 13, 2023. It is now read-only.

make "annotation constructors" actual constructors #4534

Open
CeylonMigrationBot opened this issue Sep 27, 2015 · 20 comments
Open

make "annotation constructors" actual constructors #4534

CeylonMigrationBot opened this issue Sep 27, 2015 · 20 comments

Comments

@CeylonMigrationBot
Copy link

[@gavinking] 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.

[Migrated from ceylon/ceylon-spec#1428]

@CeylonMigrationBot
Copy link
Author

[@gavinking]

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.

@CeylonMigrationBot
Copy link
Author

[@gavinking] Then this:

object foo {}

would just mean this:

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

Which is just what I want, frankly.

@CeylonMigrationBot
Copy link
Author

[@quintesse] More generic perhaps would be outer?

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

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

@CeylonMigrationBot
Copy link
Author

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

@CeylonMigrationBot
Copy link
Author

[@quintesse] 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?

@CeylonMigrationBot
Copy link
Author

[@gavinking] 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.

@CeylonMigrationBot
Copy link
Author

[@gavinking]

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.

@CeylonMigrationBot
Copy link
Author

[@gavinking]

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.

@CeylonMigrationBot
Copy link
Author

[@gavinking] Ah: body sucks because it would hammer Html { head = ... ; body = ... ; }.

And block is probably out for much the same reason.

@CeylonMigrationBot
Copy link
Author

[@gavinking] We could use out, perhaps...

@CeylonMigrationBot
Copy link
Author

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

@CeylonMigrationBot
Copy link
Author

[@gavinking] 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.

@CeylonMigrationBot
Copy link
Author

[@lucaswerkmeister] Would this also be allowed?

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

@CeylonMigrationBot
Copy link
Author

[@gavinking]
@lucaswerkmeister well currently we don't support this:

import pack { C { o { s } } }

So, no.

@CeylonMigrationBot
Copy link
Author

[@Zambonifofex] 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?

@CeylonMigrationBot
Copy link
Author

[@gavinking]

Shouldn't classes with constructors not have parameter lists?

Yes of course. Copy/paste error.

@CeylonMigrationBot
Copy link
Author

[@gavinking] 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;

@CeylonMigrationBot
Copy link
Author

[@Zambonifofex]
@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...

@CeylonMigrationBot
Copy link
Author

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

@gavinking
Copy link
Contributor

Somewhat related to this issue: annotation types look like a lot like value types defined in #6188, together with some additional restrictions. So when we get to this issue, I think we should say that annotation types are structures, and annotation constructors are their constructors.

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

No branches or pull requests

2 participants