Closed gavinking closed 11 years ago
I myself slightly prefer option 2, although we could still do 3 as well, just to make it consistent maybe with attributes. That way it's always visually obvious you're dealing with meta-model information, which I think it's a good thing in itself.
Although the combination of the two would mean @Iterator::Iterable()
to be consistent, right?
And a question, why the ()
? Why not just plain @Iterator.Iterable
?
And a question, why the
()
? Why not just plain@Iterator.Iterable
?
It's an instantiation expression.
An instantiation of what? Of a meta model object or something? Isn't that just a reference to a single object somewhere? Or is this all somehow more complex because it's a member type?
An instantiation of what?
Of the member class Iterable
of Class<Iterator>
. Of course, I doubt very much that Class
will have any such member class. It's just an illustration.
Ah ok, I was reading it like @(Iterator.Iterable())
(if such a syntax would exist). That's why it didn't make sense to me. But of course @Iterator
is/returns Class<Iterator>
which could have a member class Iterable
. Ok got it.
It may look a little "C++y" but for consistency I think ::
is the better option.
I'm going to change the syntax to Foo::Bar
and let's see if we hate it.
Hrm, at risk of opening up a can of worms, I notice that we can eliminate another ambiguity in the language by adopting the following:
"hello".size //a value reference to size
String::size //a metamodel reference to size
String.name //a value reference to the attribute name of Class<String>
Object::super.equals(that) //call a supertype implementation
Map::Entry //a metamodel reference to Entry, or the type Entry
i.e. we use ::
for metamodel references and ::super.
for calling supertypes.
Thoughts?
Object::super.equals(that)
is the only one in the list I'm not really happy with. It seems very verbose and the super
part seems to be referring to something called super
in the object's meta model. In Java I always imagined super
as being an almost real reference to the super class which you could then call methods on.
Also doesn't the Map::Entry
become ambiguous, if you can use it to either get the type itself or its metamodel reference?
Also doesn't the
Map::Entry
become ambiguous, if you can use it to either get the type itself or its metamodel reference?
That's the same thing. From a very abstract point of view, a metamodel for the type is the type, just like a reference to a function "is" the function. That's why I call it a "higher-order" language. Map::Entry
would be a type when it appears as a type, and a metamodel reference when it appears as an expression.
So after talking this through with @quintesse today, I'm left thinking that the following is approach is going to be natural and internally self-consistent:
First, in type expressions, ::
is used consistently to qualify types. For example, Iterable::Iterator
.
Then, in value expressions:
className
is a function ref of type String(Object)
and metamodel ref of type Function<String,[Object]>
to the toplevel functionSingleton<String>
is a function ref of type Singleton<String>(String)
and a metamodel ref of type Class<Singleton,[String]>
to the toplevel class@true
is a lazy ref of type Gettable<Boolean>
and a metamodel ref of type GettableValue<Boolean>
to the read-only toplevel valueOf course, the difference between true
and @true
is the following:
Boolean b = true;
Boolean b = @true.value;
Also in value expressions:
Object::equals
is a metamodel ref of type Method<Object,Boolean,Object>
to the methodobject.equals
is a function ref of type Boolean(Object)
Iterable::Iterator
is a metamodel ref of type MemberClass<Iterable,Iterator,[]>
to the member classiterable.Iterator
is a function ref of type Iterable::Iterator()
Object::@string
is a metamodel ref of type GettableAttribute<Object,String>
to the read-only attributeobject.@string
is a lazy ref of type Gettable<String>
Optionally, we could also let you write Object::string
. The difference would be the following:
String s = Object::string(object);
String s = Object::@string(object).value;
Now, what follows from this is that:
iterable.Iterator()
means Iterable::Iterator(iterable)()
object.equals(other)
means Object::equals(object)(other)
object.@string
means Object::@string(object)
object.string
would mean Object::string(object)
equals(that)
or this.equals(that)
means Object::equals(this)(that)
Furthermore, by analogy:
super.equals(that)
could mean Object::equals(super)(that)
That is to say, the syntax for disambiguating a call to a superinterface could be:
Object::equals(super)(that)
Instead of:
Object::super.equals(that)
Which is the kinda ad-hoc solution to #403 I proposed earlier.
Note that in all of the above, I'm assuming the following:
Function<Result,Args>
is a subtype of Callable<Result,Args>
Class<Result,Args>
is a subtype of Callable<Result,Args>
Method<Receiver,Result,Args>
is a subtype of Callable<Callable<Result,Args>,[Receiver]>
MemberClass<Receiver,Result,Args>
is a subtype of Callable<Callable<Result,Args>,[Receiver]>
etc.
I'm in the middle of implementing this, and it seems to be working out fine, I suppose, and I don't hate all the ::
s, but I don't love them either. Now, there is an alternative solution, that would work, though it's extremely, um, pythonic. I'm going to throw it out there just to see how you guys react...
Instead of using ::
for static qualification, we could continue to use .
. The only problem we had with that was that sometimes you could get name collisions between members of the metamodel, and members of a type. For example:
Person.name
Might be a reference to the metamodel of the member name
of Person
, or it might be an expression that evaluates the name
attribute of the Class<Person>
metamodel of the class Person
. Therefore, the typechecker would need to give you an error on the above code. There's nothing really very unceylonic about that, we have similar cases referring to members of intersection types. But it certainly would be a PITA.
But if we could reduce the possibility of name collisions to close to zero, it would not be a real problem (for rare cases of collisions, you can use member aliasing to resolve the ambiguity). Now there is a simple algorithm for this: just name all members of metamodel types with a leading _
.
Then:
Attribute<Person,String> personNameAtt = Person.name;
String personClassName = Person._name;
Honestly that's a lot less intrusive than:
Map::Entry me = HashMap().Entry();
people.map(Person::name);
Since you only see a "funny" character when you're really doing meta stuff (actually calling the metamodel). In "everyday" code, you would see the much more natural:
Map.Entry me = HashMap().Entry();
people.map(Person.name);
However, I never felt really comfortable with Python's underscores, so I'm not sure how I would feel about them here.
So basically the same solution that was proposed with @
? Person.@name
@chochos No, that's a totally different thing.
As a refinement of the above, you could perhaps add a layer of indirection between the "reference" and its metamodel object. So the expression Person
would have the type Person(Name)&Reference<Class<Person,[Name]>>
. The type Reference
would have just one member, named model
, or perhaps meta
:
String personClassName = Person.model.name;
Attribute<Person,String> personNameAtt = Person.name.model;
Stuff like see (Person)
and see (Person.name)
would continue to work, since see()
could accept a Reference
.
The risk of model
colliding with a member of a type is acceptably small, I think. You could even add a postfix operator to abbreviate that. (Yes, it has to be postfix for this to work, so don't even start!)
String personClassName = Person@.name;
Attribute<Person,String> personNameAtt = Person.name@;
But I don't personally find either of these options more readable than the _
above, FTR.
Hell, there's no reason for model
/meta
to be a member. We could make it a toplevel function and completely eliminate all ambiguity:
String personClassName = model(Person).name;
Attribute<Person,String> personNameAtt = model(Person.name);
I in fact find this extremely readable!
This might work out... wdyt?
But how would model(Person)
work when applied to the case of see(Person)
?
Forget it, it would still accept references and be able to retrieve the model from that of course.
I think you meant String
here (or at least it would be less confusing given the rest of the examples):
So the expression
Person
would have the typePerson(String)&Reference<Class<Person,[String]>>
Some slow-witted people like me might have problems understanding the above so I'll point out that important thing here is to realize that Person(String)
is a Callable
for the constructor of the type Person
. It means that the expression Person
can be used both to create new instances of Person
as well as to get the reference to the meta data.
I think you meant
String
here (or at least it would be less confusing given the rest of the examples):
Ahyeah, sure,if you prefer.
It means that the expression
Person
can be used both to create new instances ofPerson
as well as to get the reference to the meta data.
Exactly.
I certainly find model(Person.name)
less jarring than Person._name
or Person.name@
So why don't we have an Object.model
attribute like Java has Object.getClass()
?
Though we still have the problem that I would expect Person.name.model
to return the model of the Attribute
type, since Person.name
is of type Attribute
. Similarly Person.model
would be the model of the Callable
type :(
So here's a way I think we can make all this work out without introducing much in the way of new first-level constructs. It's based around the notion of metatypes, which, curiously, are not the same thing as the metamodel objects.
I've been very busy today and didn't manage to spend much time thinking about notation or naming, but bear with me, taking the syntax with a grain of salt.
Each type X
has a metatype Static<X>
. Static
is is a special type constructor built into the compiler. It is not an ordinary parameterized class or interface.
Mirrored<Interface<X>>
or Mirrored<Class<X,[P,Q]>>
.x
of X
, there is an attribute x
of Static<X>
that whose type captures the metamodel object, for example, a Mirrored<Attribute<X,T>>
, or a Mirrored<Method<X,T,[P,Q]>>
.There is a toplevel function M mirror(Mirrored<M> mirrored)
for obtaining metamodel objects, for example, mirror(X)
or mirror(X.x)
.
So, after a few days reflection, I think this is the right way to go. We should introduce a special type constructor Type<T>
which, like the type Bottom
, can't be expressed within the language. (Yeah, this is a minor contradiction of the assertion that Ceylon doesn't have types that can't be expressed within the language, but we're talking about something at the meta level here.)
Consider the following class:
class Counter(shared Integer count, shared void run()) {}
The type Type<Counter>
is isomorphic to:
interface TypeCounter
satisfies ClassReference<Counter,[Integer,Void()]> {
shared AttributeReference<Counter,Integer>> count;
shared MethodReference<Counter,Void,[]>> run;
}
Where:
ClassReference<Class,Tuple>
is a subtype of Class(Tuple) & ModelReference<Class<Class,Tuple>>
AttributeReference<Type,Value>
is a subtype of Value(Type) & ModelReference<Attribute<Type,Value>>
MethodReference<Type,Value,Tuple>
is a subtype of Value(Type)(Tuple) & ModelReference<Method<Type,Value,Tuple>>
(Note that ClassReference
, AttributeReference
, and MethodReference
will probably just be alias
es for intersection types, not actual interfaces.)
The language module would provide the following two functions:
shared Model model<Model>(ModelReference<Model> ref) { ... }
So then we can write:
Class<Counter> counterClass = model(Counter);
Attribute<Counter,Integer> countAttribute = model(Counter.count);
Method<Counter,Void,[]> runMethod = model(Counter.run);
Of course, we can continue to write stuff like:
Counter counter = .... ;
Integer count = Counter.count(counter);
Counter.run(counter)();
Since the XxxReference
types are subtypes of the appropriate function types.
I would also love to have the following operation:
shared Type<Instance> type(Instance instance) { ... }
So that we could also write:
Counter counter = .... ;
Type<Counter> counterType = type(counter);
Class<Counter> counterClass = model(counterType);
Unfortunately, according to the definitions above, Type<T>
looks invariant in T
, so that would not be well-typed. It's an issue I need to think about some more. I think it might be possible by a combination of a clever definition of Type<T>
and a special fiat rule in the language spec to make Type<T>
covariant in T
. (It's a "special" type constructor, remember!)
Finally, it's worth noticing that the "model" class Class
is a type class. The function model()
is a function that retrieves a type class for a type. It's interesting to speculate how this approach might in future be generalized to a full type class facility.
Closing, discussion moved to #403.
We're going to stick with Foo.Bar
as the syntax for qualified types. In fact, according to my proposal here, we might just remove ::
altogether.
So ::
is going away even for fully qualified names? Like ceylon.language::String
etc?
Don't think that's correct. Because those names are not part of the "language" so to speak, they're just used internally to uniquely identify a type.
@quintesse Right. Indeed, it's possible that one day (Not in Ceylon 1.0) we might add a real scoping operator named ::
. But that would be nothing to do with qualified types as such.
I'm a little confused with regards to your Type
proposal. I don't want to distract from the 1.0 push right now, though, but please pull me back in when this topic comes up again. There are I few things I'd like to check.
I guess Type
can't be a subtype of Object
since It can't have members such as string
, hash
and equals
.
@FroMage Well, I suppose the ambiguity of Person.string
is maybe a good reason to simply abandon this syntax entirely. We could, I guess, make some of these problems go away if we change the syntax a bit...
I think we should take seriously the original option 3 above. What about the following:
A toplevel function metamodel reference:
@coalesce
A toplevel class metamodel reference:
@Person
A toplevel class member metamodel reference:
Object@string
A nested type metamodel reference:
Map@Entry
A nested type member metamodel reference:
Map.Entry@key
Now, it is going to suck that we will have to write see(@Person)
instead of see (Person)
, but I guess I think it's something I can live with...
I sorta like this idea, and I support it is better than a model
operator.
But I think using @
will be confusing for Java users, especially if it appears in annotations. Not only that but I haven't given up on convincing you to allow @AnnotationType()
as an alternative to annotationConstructor()
which would make Java interop a lot nicer and be a nice option for people writing annotations declarations that don't want to write two declarations (type and constructor method) every time.
Perhaps ::
would work instead of @
for model references?
I think ::Person
is just nasty. Same for ::coalesce
.
Plus I definitely don't think we should be warping the syntax of Ceylon in order to address a Java interop problem.
If I thought it was just an interop problem I wouldn't even mention it. I think our syntax for declaring annotations is as bad as our syntax for declaring enums: it's way more verbose than Java's, and it is going to hit us.
What about &Person
then? I don't remember if we still have a unary &
operator.
We were once discussing using #
, that doesn't have prior connotations, does it?
We don't have a prefix &
. But nor do I support the idea of having &
mean two different totally unrelated things depending upon how it occurs in the expression syntax.
#FFFF
is a hexadecimal literal.
Argh, true
We explicitly set aside @
for use in this context about a year ago, and I've jealously guarded it ever since, because I knew we were going to need it. All other symbols are already taken, with the exception of some doubled symbols like ::
.
Based on statistical evidence that nobody ever uses union, intersections and xor on collections, let's remove these operators and free them. Then we can use &
;)
We should just set up a business on the side selling Ceylon keyboards that contain a dozen extra symbols
Based on statistical evidence that nobody ever uses union, intersections and xor on collections, let's remove these operators and free them.
These symbols are of course already taken for type expressions. You definitely can't use them for distinguishing a metamodel ref.
Ah, those ones…
The prefix/infix @
doesn't work especially well for union/intersection types. But neither did the previous syntax. If we wanted union/intersection metamodel refs, we would need something more like a quoting syntax. Backticks would be the only available natural option, I suppose. I think they're still available.
A toplevel function metamodel reference:
`coalesce`
A toplevel class metamodel reference:
`Person`
A toplevel class member metamodel reference:
`Object.string`
A nested type metamodel reference:
`Map.Entry`
A nested type member metamodel reference:
`Map.Entry.key`
A union type metamodel reference:
`Integer|Float`
An abbreviated type metamodel reference:
`String[]`
I'm not sure which is preferable out of
see(@Person)
and
see(`Person`)
Just to add a pretty horrible suggestion for completeness sake.
Given the fact we already have < >
for grouping you could possibly do:
@<Integer|Float>
and
@<String[]>
@quintesse Well, sure, you could use any of @< .... >
, @[ ... ]
, or @( ... )
but none of those are better than backticks, I don't think.
Belatedly, I've noticed a potential inconsistency in the syntax for qualified types and metamodel references.
In principal, if we defined the metamodel type
Class
to have a member type,Iterator
, for example, then the following phenomenon would occur:Iterable.Iterator it
would refer to the member type ofIterable
, butIterable.Iterator()
would refer to the member type ofClass
.This is not an actual ambiguity as such, but it's certainly undesirable. Of course, it's a phenomenon that will never occur in practice, if we simply don't define any member classes of the metamodel types.
A second related observation is that with the introduction of the syntax
Iterable::Iterator
in #403 we have gained a second redundant way to express a qualified type. This is also undesirable, I believe.There's three ways to handle this that I can see:
Iterable.Iterator
, and.
is much easier on the eyes than::
or@
. It's not especially strange that type-level operators have slightly different semantics to value-level operators—just look at|
and&
. Hell, we didn't even notice this wrinkle until now, so the chance of anyone else actually noticing it is somewhere in the range-1p..+1p
.::
for qualifying types: we now have a totally reasonable and unambiguous syntax for writing qualified types. The semantics of #403'sFoo::Bar
is a simply much better and more natural fit here than the semantics of.
. We're be adopting a really strained second interpretation of.
, just because we think it looks slightly prettier, and because we're used to it. Why have two totally synonymous ways to qualify a type name?@
: we already need that for attribute references. Why not force you to write, for example@Iterator.Iterable()
, making it completely unambiguous that you're talking about the metamodel here. ThenIterator.Iterable
could mean exactly the same thing wherever it appears.I'm quite uncertain which option I prefer. Option 2, perhaps.