eclipse-archived/ceylon

allow constructors to be called as `Foo.ctor<String>`

Closed this issue · 6 comments

I’ve been somewhat bothered by the fact you instantiate generic classes through constructors as Foo<String>.ctor, but you access static members as Foo.member<String>.

Something occurred to me a couple months ago: we can generalize the behavior we have for static access of instance members to static member access, but, instead of doing so with value argument lists, we do it for type argument lists.

That is, consider the following classes:

class Foo<Argument>
{
	shared static String member = `Argument`.string;
	
	shared new ctor()
	{
	}
}

class Bar(String argument)
{
	shared String member = argument;
}

Since we can do Bar("string").member, and it means the same as Bar.member("string"), then we could say one can do Foo<String>.ctor and it would mean the same as Foo.ctor<String>.

It’s more difficult to do the opposite and have Foo.member<String> mean the same as Foo<String>.member, as Argument need to be in scope for member, and its value might change depending on the argument provided. Of course, it’s not impossible (as the Foo<String> in Foo<String>.member is not an expression), but it’s more unintuitive.

Further, we could generalize this behavior to regular instance members too. That is, consider the following class:

class Baz<ClassArgument>(String classArgument)
{
	shared String member<MemberArgument>(Integer memberArgument) => "hello!";
}

We could say that one could write all of the following and they would all mean the same thing:

  • Baz<String>("string").member<Integer>(123)
  • Baz<String>.member("string")<Integer>(123)
  • Baz.member<String>("string")<Integer>(123)

Of course, the grammar doesn’t allow things such as foo("string")<String> today, but the grammar could easily change.

Also, it’s interesting to note that this makes the inference for the type arguments of a constructor’s class feel more at home.

So, I figured I’m dumb. One can already write Foo<String>.member. I was thinking you can’t because the following is already possible:

shared void run()
{
    value member = Foo.member;
    value res = member<String>;
    print(res);
}

However, I still hold to my proposed idea on constructors.

@Zambonifofex this would be the calling protocol for a generic constructor (see #335 and #4419).

So, no, I definitely don't think that we should let the type arguments of the containing class be supplied as arguments to the member of the class.

I would close this issue but I still don't have permissions :-(

Higher rank generics (#3860), call for a change in the grammar that would allow people to supply subsequential type argument lists.

That is, if you have a shared new foo<Bar>() in a class Foo<Baz>, then you would be able to call it as Foo.foo<Float><Integer>(), where Float and Integer are arguments to Baz and Bar, respectively.

By the way, what about the following?

Also, it’s interesting to note that this makes the inference for the type arguments of a constructor’s class feel more at home.

My proposal would mean that all type argument inference is done when passing a regular argument list to a generic whose result is Callable, or when passing such a generic as an argument to a parameter of type Callable.

So, instead of opening an exception for constructors, where the type argument list is added elsewhere from the regular argument list, this makes their type argument inference fit within the rest of the inference rules of the language.

Either way, if you can confirm that that’s still not a change you consider, I can close this issue for you. I would just appreciate if you could at least explain it.

Higher rank generics (#3860), call for a change in the grammar that would allow people to supply subsequential type argument lists.

OK, sure, that's a pretty good point, and so, indeed, this feature would fall out naturally from allowing type arguments to indirect (generic) function references in the higher rank generics facility.

However, again, I definitely wouldn't engineer in the more limited feature proposed here as a special case of the more general thing. I would do the full thing or nothing at all. And so I still think this issue can be closed.

Okay, that’s fine.

@Zambonifofex note that you can already write stuff like this and it works:

class Foo<X> {
    shared new foo(X x) {}
}

value foo = Foo.foo;

value ref = foo<String>;
value result = foo<String>("hello"); //or just let <String> be inferred

So I don't think that much is missing here.