This proposal introduces a set of features designed to work together to solve some of the many problems associated with Fantom constructors.
Problem
Summary of problems we wish to solve:
the special Foo() syntax is only available to methods named make and fromStr
static methods used as factory constructors create problems because they inherit into the namespace of subclasses (causing problems with the "good" name)
serialization requires a no-arg constructor called make which burns you single "good" constructor for shortcut syntax
auto-generation of boilerplate it-block constructor
Lets consider a simple example:
class A { static A make() { ... } }
class B : A {}
In this case A defines a static factory method called make. But since static methods inherit into B, it means that we cannot define a B.make method. This forces come up with another name for the B constructor, which often is lame and means we can no longer use the good syntax for creating instances of B.
Summary of Changes
This proposal includes the following changes:
enhance default auto-generated constructor to be the following (keeps auto-generation rules simple, but now works with const classes)
make(|This|? f := null) { if (f != null) f(this) }
introduction of static constructors annotated with static new keywords which are treated as factory methods
enhancement of construction expression syntax to work with any method marked with new keyword (instance constructor or static constructors)
Construction Expressions
Today we overload by parameter type the expression Foo() to map to either Foo.make or Foo.fromStr methods. We also overload operators by parameter type. In this proposal we open up all constructors annotated with the new keyword to be called using the shortcut syntax:
class Foo
{
new make() {}
new makeInt(Int x) {}
new makeStr(Str x) {}
}
Foo() => Foo.make()
Foo(3) => Foo.makeInt(3)
Foo("x") => Foo.makeStr("x")
Of course with parameter overloading we might introduce ambiguity, in which case you would have to switch to the named version of the constructor. Note because every constructor still has a unique name we still have clean reflection and dynamic dispatch.
Static Constructors
One thing that I really like about Fantom is that from a "client" perspective there is no difference between an instance constructor and a static method that acts like a constructor:
static Foo make(Str x) {...}
new make(Str x) {}
In both of these cases I call them the exactly same way:
But we don't necessarily capture developer intent when using a static method as constructor factory. So I propose to introduce a formal annotation for these factory methods by combining the new and static keywords:
class Foo
{
static new make(Str x) { FooImpl(x) }
}
They would work just like any static method, but with a few special aspects:
They are implied to return declaring class, in case above it is implied to return Foo?
According to existing rules, methods flagged with new keyword are not inherited into subclass namespace. So they will not conflict with methods in subclass namespace. This solves a lot problems people are having with factory methods on base classes and mixins.
Any method flagged with new (either static or instance) gets to take advantage of shortcut syntax. We now have a way to know that a given method is intended to be used for construction
Breaking Changes
These are the breaking changes this proposal would entail:
All fromStr methods would have to be changed to use new syntax (to take advantage of shortcut syntax)
// old version
static Int? fromStr(Str s, Int radix := 10, Bool checked := true)
// new version
static new fromStr(Str s, Int radix := 10, Bool checked := true)
For next build, I think suggest add warning to any method fromStr not marked as new and implicitly add the new flag. This will keep most things working until code can get updated.
Same goes for static make methods.
The other breaking change would be that in some cases the construction expression might result in ambiguity errors due to ambiguous parameter overloading (since now all constructors will be available). I don't think this will happen too much in practice, and its easy fix to just use the actual constructor name.
Andy and I spent quite a bit of time evaluating different designs. I really dig this design because it combines the idea of static methods and constructor methods quite elegantly with other rules such as inheritance and construction expressions. Please let me know what you think. I'm shooting to add these changes into the upcoming Fantom build sometime late next week.
qualidafialThu 16 Feb 2012
+1 with one change:
As mentioned in #1775, the function argument to the default constructor should be mandatory if any fields on the object need to be explicitly initialized. i.e. const fields or non-nullable fields.
This gives the compiler a chance to bark at you for failing to provide an initializer when static analysis can prove it's needed.
qualidafialThu 16 Feb 2012
So basically we're using the new keyword to tackle constructor shortcuts, similar to how we used the @Operator facet to tackle operator overloading?
alexlamslThu 16 Feb 2012
Would you mind giving an example to illustrate the difference between new and static new?
I am a bit confused as to why new is not inherently static...
kevinpenoFri 17 Feb 2012
I'm confused why the different overloads of new need to be named differently. Why can't fantom just support overloading?
@alexlamsl constructor is not just a usual method because it hasn't return value and it should initialize object state. However from the user perspective object creation looks like call to static method. And sometimes it's useful to have factory static method instead of constructor. But any static method always delegates to some constructor.
KevinKelleyFri 17 Feb 2012
Really like this proposal, along with #1775, it answers a lot of niggling little issues I've had. +1.
Unifying constructors and static factories, with new and static new, seems like the kind of thing that's so obvious you wonder why it wasn't always that way. Big fan.
lbertrandFri 17 Feb 2012
Really like this as it addresses some of the annoyance encountered when using Fantom...
A comment though:
to overload constructors, we use the new keyword. To overload operators, we use a facet @Operator. Seems inconsistent to me: a facet in one case and a keyword in another case... I feel that transforming the facet to a keyword will not hurt.
And a question:
Is the with function still needed with this proposal? I can see the with function just useful for a copy constructor of non const classes, is this correct?
qualidafialFri 17 Feb 2012
@lbertrand: with is still needed. The |This| constructor is only the default, and can be overridden with an explicit constructor that doesn't take a Func.
tcolarFri 17 Feb 2012
+1
lbertrandThu 23 Feb 2012
Any comment on my remark about inconsistency between operator overload and constructor overload?
to overload constructors, we use the new keyword. To overload operators, we use a facet @Operator. Seems inconsistent to me: a facet in one case and a keyword in another case... I feel that transforming the facet to a keyword will not hurt.
brianThu 23 Feb 2012
Any comment on my remark about inconsistency between operator overload and constructor overload?
They are similar in that they are two cases where we allow overloading. But I also think they are two very beasts. Operators are using symbols and are really mapped using special prefix naming rules. Constructors are designed to look like factories, don't use symbols, and don't work within the prefix naming bounds. Plus there are tons of extra special things we do with constructors such as statement checking, etc. This was the whole reason that using the @Operator facet I original proposed didn't pan out.
brianThu 23 Feb 2012
Promoted to ticket #1776 and assigned to brian
brianThu 23 Feb 2012
Ticket resolved in 1.0.62
This work has been completed minus the changes to the auto-generated make constructor. I'm very happy with how it turned out now that I've gotten a chance to see how it works across all our codebases.
I found only 2 or 3 ambiguity errors in Fantom codebase, and about a dozen in our commercial codebase (all cases were ambiguity due to auto-casting and things probably deserved to be clarified anyhow).
lbertrandThu 23 Feb 2012
I understand behind the scene the 2 are different - but this is a problem of the compiler... And the facet is just an indicator, do not carry any extra information here.
For a developer writing a class, I feel that both approach should follow the same convention and I think moving toward a specific keyword to overload operators is closer to the new keyword to overload constructors instead of using a facet in one case and not in the other...
I can't explain why but to me a facet should carry different/extra information than something which is part of the language and the compiler... It feel to me here than we are going the Java way which is to use annotations for all and everything!
So instead of this
class Foo
{
new make() { ... }
@Operator Int plusInt(Int x) { ... }
@Operator Float plusFloat(Float x) { ... }
}
I will prefer
class Foo
{
new make() { ... }
operator Int plusInt(Int x) { ... }
operator Float plusFloat(Float x) { ... }
}
This is just an opinion and I can live with it - this is more cosmetic than an issue to be resolved... But I think it will keep the language very nice!
brianFri 24 Feb 2012
I thought you were arguing that constructors should use a facet. I can't argue that it might be nice to make operator a keyword. But taking keywords is a really severe thing that steals that identifier from all programs and make interaction with Java libraries that use that identifier awkward. Facets pollute the type namespace, but seem much less severe in their name stealing (especially with using as). So I think in general any feature like operators used on extremely few classes doesn't deserve a keyword.
brian Thu 16 Feb 2012
This proposal introduces a set of features designed to work together to solve some of the many problems associated with Fantom constructors.
Problem
Summary of problems we wish to solve:
Foo()
syntax is only available to methods namedmake
andfromStr
make
which burns you single "good" constructor for shortcut syntaxLets consider a simple example:
In this case
A
defines a static factory method calledmake
. But since static methods inherit intoB
, it means that we cannot define aB.make
method. This forces come up with another name for theB
constructor, which often is lame and means we can no longer use the good syntax for creating instances ofB
.Summary of Changes
This proposal includes the following changes:
static new
keywords which are treated as factory methodsnew
keyword (instance constructor or static constructors)Construction Expressions
Today we overload by parameter type the expression
Foo()
to map to eitherFoo.make
orFoo.fromStr
methods. We also overload operators by parameter type. In this proposal we open up all constructors annotated with thenew
keyword to be called using the shortcut syntax:Of course with parameter overloading we might introduce ambiguity, in which case you would have to switch to the named version of the constructor. Note because every constructor still has a unique name we still have clean reflection and dynamic dispatch.
Static Constructors
One thing that I really like about Fantom is that from a "client" perspective there is no difference between an instance constructor and a static method that acts like a constructor:
In both of these cases I call them the exactly same way:
But we don't necessarily capture developer intent when using a static method as constructor factory. So I propose to introduce a formal annotation for these factory methods by combining the
new
andstatic
keywords:They would work just like any static method, but with a few special aspects:
Foo?
new
keyword are not inherited into subclass namespace. So they will not conflict with methods in subclass namespace. This solves a lot problems people are having with factory methods on base classes and mixins.new
(either static or instance) gets to take advantage of shortcut syntax. We now have a way to know that a given method is intended to be used for constructionBreaking Changes
These are the breaking changes this proposal would entail:
All
fromStr
methods would have to be changed to use new syntax (to take advantage of shortcut syntax)For next build, I think suggest add warning to any method
fromStr
not marked asnew
and implicitly add thenew
flag. This will keep most things working until code can get updated.Same goes for
static make
methods.The other breaking change would be that in some cases the construction expression might result in ambiguity errors due to ambiguous parameter overloading (since now all constructors will be available). I don't think this will happen too much in practice, and its easy fix to just use the actual constructor name.
Andy and I spent quite a bit of time evaluating different designs. I really dig this design because it combines the idea of static methods and constructor methods quite elegantly with other rules such as inheritance and construction expressions. Please let me know what you think. I'm shooting to add these changes into the upcoming Fantom build sometime late next week.
qualidafial Thu 16 Feb 2012
+1 with one change:
As mentioned in #1775, the function argument to the default constructor should be mandatory if any fields on the object need to be explicitly initialized. i.e. const fields or non-nullable fields.
This gives the compiler a chance to bark at you for failing to provide an initializer when static analysis can prove it's needed.
qualidafial Thu 16 Feb 2012
So basically we're using the
new
keyword to tackle constructor shortcuts, similar to how we used the@Operator
facet to tackle operator overloading?alexlamsl Thu 16 Feb 2012
Would you mind giving an example to illustrate the difference between
new
andstatic new
?I am a bit confused as to why
new
is not inherentlystatic
...kevinpeno Fri 17 Feb 2012
I'm confused why the different overloads of
new
need to be named differently. Why can't fantom just support overloading?Edit: nvm, I just found this
Yuri Strot Fri 17 Feb 2012
This is amazing change!
@alexlamsl constructor is not just a usual method because it hasn't return value and it should initialize object state. However from the user perspective object creation looks like call to static method. And sometimes it's useful to have factory static method instead of constructor. But any static method always delegates to some constructor.
KevinKelley Fri 17 Feb 2012
Really like this proposal, along with #1775, it answers a lot of niggling little issues I've had. +1.
Unifying constructors and static factories, with
new
andstatic new
, seems like the kind of thing that's so obvious you wonder why it wasn't always that way. Big fan.lbertrand Fri 17 Feb 2012
Really like this as it addresses some of the annoyance encountered when using Fantom...
A comment though:
new
keyword. To overload operators, we use a facet@Operator
. Seems inconsistent to me: a facet in one case and a keyword in another case... I feel that transforming the facet to a keyword will not hurt.And a question:
with
function still needed with this proposal? I can see thewith
function just useful for a copy constructor of nonconst
classes, is this correct?qualidafial Fri 17 Feb 2012
@lbertrand:
with
is still needed. The|This|
constructor is only the default, and can be overridden with an explicit constructor that doesn't take aFunc
.tcolar Fri 17 Feb 2012
+1
lbertrand Thu 23 Feb 2012
Any comment on my remark about inconsistency between operator overload and constructor overload?
brian Thu 23 Feb 2012
They are similar in that they are two cases where we allow overloading. But I also think they are two very beasts. Operators are using symbols and are really mapped using special prefix naming rules. Constructors are designed to look like factories, don't use symbols, and don't work within the prefix naming bounds. Plus there are tons of extra special things we do with constructors such as statement checking, etc. This was the whole reason that using the
@Operator
facet I original proposed didn't pan out.brian Thu 23 Feb 2012
Promoted to ticket #1776 and assigned to brian
brian Thu 23 Feb 2012
Ticket resolved in 1.0.62
This work has been completed minus the changes to the auto-generated
make
constructor. I'm very happy with how it turned out now that I've gotten a chance to see how it works across all our codebases.I found only 2 or 3 ambiguity errors in Fantom codebase, and about a dozen in our commercial codebase (all cases were ambiguity due to auto-casting and things probably deserved to be clarified anyhow).
lbertrand Thu 23 Feb 2012
I understand behind the scene the 2 are different - but this is a problem of the compiler... And the facet is just an indicator, do not carry any extra information here.
For a developer writing a class, I feel that both approach should follow the same convention and I think moving toward a specific keyword to overload operators is closer to the new keyword to overload constructors instead of using a facet in one case and not in the other...
I can't explain why but to me a facet should carry different/extra information than something which is part of the language and the compiler... It feel to me here than we are going the Java way which is to use annotations for all and everything!
So instead of this
I will prefer
This is just an opinion and I can live with it - this is more cosmetic than an issue to be resolved... But I think it will keep the language very nice!
brian Fri 24 Feb 2012
I thought you were arguing that constructors should use a facet. I can't argue that it might be nice to make operator a keyword. But taking keywords is a really severe thing that steals that identifier from all programs and make interaction with Java libraries that use that identifier awkward. Facets pollute the type namespace, but seem much less severe in their name stealing (especially with using as). So I think in general any feature like operators used on extremely few classes doesn't deserve a keyword.