because Sample2.make conflicts with Sample.make. Is it possible to use something similar to constructor chaining or just allow to "overlap" static method name with another static method in an inheritor?
brianSat 22 Oct 2011
Yes, this is one place where the compiler and language's distinction between constructor and static is limiting.
Just to set stage for the principles at play here:
each type may have exactly one slot (field/method) mapped to a given name
static and instance methods are inherited
constructors are not inherited
So the reason that we can overload constructor names is because they not are inherited. You cannot reflect nor call them directly from subclasses. But static methods are available for reflection and for un-scoped call in subclasses, which is why overloading them by name would cause a lot of problems. So I don't think we want to allow static methods in a subclass to hide the name of a static method in a superclass - it breaks Fantom's fundamental idea of types being simple maps keyed only by name.
But in this case the real feature at play here is how we allow special names like make and fromStr to be used as "constructor shortcuts". Just throwing some ideas out there, but maybe we should allow that on any named method using a facet?
qualidafialSat 22 Oct 2011
I like the facet idea with constructor shortcuts.
This would make compiler magic more apparent when reading the code, like @Operator did.
What would this facet be called? @Cast?
qualidafialSat 22 Oct 2011
We could also apply this facet to toX instance methods on the source type, e.g. Str.toInt could be invoked using:
Str input := 5
Int value := Int(input)
Although you could get conflicts if both the source and destination type declare conversion methods.
Yuri StrotSat 22 Oct 2011
@brian I really like this idea! What do you think about @New facet? It's short, close to new keyword and allows to find all constructors easily.
From the other side it would be nice to have clear rule for selection of constructor shortcut by arguments.
@qualidafial I think it makes sense to keep code unambiguous both for programmer and compiler. When I see Int(input) I expect to find creation method in the source of Int type.
brianTue 25 Oct 2011
Regarding the facet, I think the right move is to just use the @Operator facet. A constructor can then just be another category of facets prefixed with "make" or perhaps "from".
What I'm struggling with is how this ties into overloading and the existing support for "make" and "fromStr". We made a change a while back to overload operators by parameter such as Int.plus and Int.plusFloat. Should we do same for constructor shortcut?
Also should we require tagging "make" and "fromStr" with @Operator or is it somehow implied? I strongly dislike going back and requiring that facet on things like "make", but at the same time I dislike the inconsistency it would introduce to make them special.
Yuri StrotTue 25 Oct 2011
At a glance make doesn't fit @Operator approach well:
Image.make doesn't mean make(Image)
Image.makePainted doesn't mean make(Pained)
However it might be good generalization for fromStr: A.from(A), A.fromStr(Str), A.fromB(B), etc. In this case fromStr is just one more @Operator.
But I'm not sure how it can fix my original issue:
At issue here is that conversion e.g. static A fromB(B) { ... } is conflated in the syntax with construction e.g. new makeB(B) { ... }.
I think we should first discuss and decide whether these two concepts are worth distinguishing from each other.
If so, then they should probably be syntactically distinct.
Just my $.0200000000001 :)
qualidafialTue 25 Oct 2011
On one hand, there is an elegant symmetry between toStr and fromStr method names which I like.
On the other hand, explicit calls to fromStr are probably rare compared to the constructor syntax. So maybe this symmetry is only appreciated in the docs.
Another option might be to elevate from methods to constructor status. This would loosen rules on constructor names, while unifying the shorthand constructor syntax similar to how @Operator unified operator usage.
jodastephenThu 27 Oct 2011
I would say that type conversion and construction are really two different things. That is why there are two different method names - make and from. Conflating them is less than ideal - a type conversion factory should build on a constructor, not be a constructor.
The thread on the ~ operator covered this area too. As a reminder, it proposed using ~ as the operator for from/'to'. The Type() pattern is then the "constructor operator".
Treated separately, it might then be reasonable to have to mark fromStr with @Operator, but not make (ie. saying that construction is not an operator). I'd also note that type conversion generally needs overloading, whereas construction tends not to.
The original problem here reminds me of this thread which is a pretty close description of the problem with a proposed solution. (Static in Fantom remains weak and a language anomoly IMO). Thus I'd permit:
mixin Sample {
static Sample make(Str text)
}
which simply requires the implemening class to provide a method matching that signature.
Yuri StrotThu 3 Nov 2011
Thanks for all comments! There are a lot of interesting discussions around type creation, conversion, constructor shortcuts, etc. But let me back to static inheritance and clarify one thing.
I see 5 different members of the type definition: methods, fields, static methods, static fields and constructors. All these members well defined with the sys::Slot class: they have parent type, signature, visibility, facets and hopefully some documentation.
I'd like to notice static slots are very different from the instance slots. I mean static is not just one more property of a method/field. For example, let's compare API of static VS instance fields as if they were two different types:
const class InstanceField : Slot
{
Obj? get(Obj instance)
Void set(Obj instance, Obj? value)
Type type()
}
const class StaticField : Slot
{
Obj? get()
Void set(Obj? value) // Not sure this method makes sense at all
Type type()
}
Acutally I don't think we should have two different types, current API is clear and simple enough. I'm also find very useful to identify any slot by type+name. But I don't understand why static slots are inherited as instance slots, while they look more like constructors from this perspective.
For instance, classes in JavaFX can't have static members and you should define all static stuff at the script level, outside of the class definition scope: http://devjfx.wordpress.com/2010/03/26/static-fields-and-functions-in-javafx/. I think it's an interesting idea to highlight that static members do not join usual life cycle of instance members inheritance/overloading.
To be more concrete, in the following case:
class A { static Void a() }
class B : A {}
I find B.a() incorrect the same as A().a().
brianFri 4 Nov 2011
I would say that type conversion and construction are really two different things. That is why there are two different method names - make and from.
I sort of agree, but mostly disagree. What matters is that you are constructing a new object. There might be some conversions that aren't actually constructors, but I think in most cases conversation is just one case of construction. Maybe technically a better naming convention might have been makeFromStr or something.
I think it's an interesting idea to highlight that static members do not join usual life cycle of instance members inheritance/overloading.
To me the key issue is that namespace inheritance works the same as reflection inheritance:
class A { static Void foo() {} }
class B : A { Void main() { foo } )
In a subclass like B, we can call A.foo without explicitly saying A.foo because we inherited static methods. Note we cannot to that with constructors - they are inherited neither in namespace scope nor by reflection.
If we decided to treat static methods like constructors, then the whole namespace scoping and reflection issue would solved. However it would be a huge breaking change and I think in many cases be fairly annoying to anyone who expects Java like behavior.
I still think the original use case might be better solved with something like the @Operator facet. We already previously decided that math operators like + deserved to be overloaded by parameter type and came up with a design that let you do that, but still forced unique method names. I think same principles apply to construction.
jodastephenSun 6 Nov 2011
> I would say that type conversion and construction are really two different things. That is why there are two different method names - make and from.
I sort of agree, but mostly disagree. What matters is that you are constructing a new object. There might be some conversions that aren't actually constructors, but I think in most cases conversation is just one case of construction. Maybe technically a better naming convention might have been makeFromStr or something.
If the code is a standalone variable declaration, then it looks like construction. But if it is passing a foo to a method that takes a bar, then it is conversion. The two are related but distinct.
Construction is about creating an object from its separate parts (and should involve little complexity). Conversion typically involves more logic, such as parsing.
As an example, you cannot construct a Decimal/Float/Int from one of the other types. The only way it can be done is to call the toXxx method on the one you have. It might make sense to add:
Float.fromDecimal
Float.fromInt
and so on, using @Operator overloading.
The problem with all this (as shown with the current number situation) is that the user has to know whether the conversion method is on type A or type B. ie:
A.fromB(b) // or
b.toA()
(Unless you code it on both, which won't always be possible).
That is where the ~ operator comes in, to focus attention on the task of conversion, not the exact mechanism/method used to achieve it.
The more interesting case is where you have two types that don't know about each other, but you want to write conversion logic. Scala's implicits allows this - arbitrary pieces of logic to convert between two types, but it is not easy. For example, Decimal has method plusInt, but really this represents an aspect of the combination of Int and Decimal, and probably shouldn't be a method on either.
Thus, the "correct" solution is one where all aspects of the relationship between two classes is represented by a "junction" class:
Yuri Strot Sat 22 Oct 2011
I'm trying to combine mixin with static make method to have interface and factory in one place:
Now I can use
Sample(text)
syntax to create mixin which looks amazing.However there is one disadvantage with static make compared to ctor - it can't be inherited. In my case I can't use:
because
Sample2.make
conflicts withSample.make
. Is it possible to use something similar to constructor chaining or just allow to "overlap" static method name with another static method in an inheritor?brian Sat 22 Oct 2011
Yes, this is one place where the compiler and language's distinction between constructor and static is limiting.
Just to set stage for the principles at play here:
So the reason that we can overload constructor names is because they not are inherited. You cannot reflect nor call them directly from subclasses. But static methods are available for reflection and for un-scoped call in subclasses, which is why overloading them by name would cause a lot of problems. So I don't think we want to allow static methods in a subclass to hide the name of a static method in a superclass - it breaks Fantom's fundamental idea of types being simple maps keyed only by name.
But in this case the real feature at play here is how we allow special names like
make
andfromStr
to be used as "constructor shortcuts". Just throwing some ideas out there, but maybe we should allow that on any named method using a facet?qualidafial Sat 22 Oct 2011
I like the facet idea with constructor shortcuts.
This would make compiler magic more apparent when reading the code, like
@Operator
did.What would this facet be called?
@Cast
?qualidafial Sat 22 Oct 2011
We could also apply this facet to
toX
instance methods on the source type, e.g.Str.toInt
could be invoked using:Although you could get conflicts if both the source and destination type declare conversion methods.
Yuri Strot Sat 22 Oct 2011
@brian I really like this idea! What do you think about
@New
facet? It's short, close tonew
keyword and allows to find all constructors easily.From the other side it would be nice to have clear rule for selection of constructor shortcut by arguments.
@qualidafial I think it makes sense to keep code unambiguous both for programmer and compiler. When I see
Int(input)
I expect to find creation method in the source ofInt
type.brian Tue 25 Oct 2011
Regarding the facet, I think the right move is to just use the
@Operator
facet. A constructor can then just be another category of facets prefixed with "make" or perhaps "from".What I'm struggling with is how this ties into overloading and the existing support for "make" and "fromStr". We made a change a while back to overload operators by parameter such as
Int.plus
andInt.plusFloat
. Should we do same for constructor shortcut?Also should we require tagging "make" and "fromStr" with @Operator or is it somehow implied? I strongly dislike going back and requiring that facet on things like "make", but at the same time I dislike the inconsistency it would introduce to make them special.
Yuri Strot Tue 25 Oct 2011
At a glance
make
doesn't fit@Operator
approach well:Image.make
doesn't meanmake(Image)
Image.makePainted
doesn't meanmake(Pained)
However it might be good generalization for
fromStr
:A.from(A)
,A.fromStr(Str)
,A.fromB(B)
, etc. In this casefromStr
is just one more @Operator.But I'm not sure how it can fix my original issue:
May be something like ability to reassign default creation method:
qualidafial Tue 25 Oct 2011
At issue here is that conversion e.g.
static A fromB(B) { ... }
is conflated in the syntax with construction e.g.new makeB(B) { ... }
.I think we should first discuss and decide whether these two concepts are worth distinguishing from each other.
If so, then they should probably be syntactically distinct.
Just my $.0200000000001 :)
qualidafial Tue 25 Oct 2011
On one hand, there is an elegant symmetry between
toStr
andfromStr
method names which I like.On the other hand, explicit calls to
fromStr
are probably rare compared to the constructor syntax. So maybe this symmetry is only appreciated in the docs.Another option might be to elevate
from
methods to constructor status. This would loosen rules on constructor names, while unifying the shorthand constructor syntax similar to how@Operator
unified operator usage.jodastephen Thu 27 Oct 2011
I would say that type conversion and construction are really two different things. That is why there are two different method names -
make
andfrom
. Conflating them is less than ideal - a type conversion factory should build on a constructor, not be a constructor.The thread on the ~ operator covered this area too. As a reminder, it proposed using
~
as the operator forfrom
/'to'. TheType()
pattern is then the "constructor operator".Treated separately, it might then be reasonable to have to mark
fromStr
with@Operator
, but notmake
(ie. saying that construction is not an operator). I'd also note that type conversion generally needs overloading, whereas construction tends not to.The original problem here reminds me of this thread which is a pretty close description of the problem with a proposed solution. (Static in Fantom remains weak and a language anomoly IMO). Thus I'd permit:
which simply requires the implemening class to provide a method matching that signature.
Yuri Strot Thu 3 Nov 2011
Thanks for all comments! There are a lot of interesting discussions around type creation, conversion, constructor shortcuts, etc. But let me back to static inheritance and clarify one thing.
I see 5 different members of the type definition: methods, fields, static methods, static fields and constructors. All these members well defined with the
sys::Slot
class: they have parent type, signature, visibility, facets and hopefully some documentation.I'd like to notice static slots are very different from the instance slots. I mean
static
is not just one more property of a method/field. For example, let's compare API of static VS instance fields as if they were two different types:Acutally I don't think we should have two different types, current API is clear and simple enough. I'm also find very useful to identify any slot by type+name. But I don't understand why static slots are inherited as instance slots, while they look more like constructors from this perspective.
For instance, classes in JavaFX can't have static members and you should define all static stuff at the script level, outside of the class definition scope: http://devjfx.wordpress.com/2010/03/26/static-fields-and-functions-in-javafx/. I think it's an interesting idea to highlight that static members do not join usual life cycle of instance members inheritance/overloading.
To be more concrete, in the following case:
I find
B.a()
incorrect the same asA().a()
.brian Fri 4 Nov 2011
I sort of agree, but mostly disagree. What matters is that you are constructing a new object. There might be some conversions that aren't actually constructors, but I think in most cases conversation is just one case of construction. Maybe technically a better naming convention might have been
makeFromStr
or something.To me the key issue is that namespace inheritance works the same as reflection inheritance:
In a subclass like B, we can call A.foo without explicitly saying
A.foo
because we inherited static methods. Note we cannot to that with constructors - they are inherited neither in namespace scope nor by reflection.If we decided to treat static methods like constructors, then the whole namespace scoping and reflection issue would solved. However it would be a huge breaking change and I think in many cases be fairly annoying to anyone who expects Java like behavior.
I still think the original use case might be better solved with something like the
@Operator
facet. We already previously decided that math operators like+
deserved to be overloaded by parameter type and came up with a design that let you do that, but still forced unique method names. I think same principles apply to construction.jodastephen Sun 6 Nov 2011
If the code is a standalone variable declaration, then it looks like construction. But if it is passing a foo to a method that takes a bar, then it is conversion. The two are related but distinct.
Construction is about creating an object from its separate parts (and should involve little complexity). Conversion typically involves more logic, such as parsing.
As an example, you cannot construct a Decimal/Float/Int from one of the other types. The only way it can be done is to call the toXxx method on the one you have. It might make sense to add:
and so on, using @Operator overloading.
The problem with all this (as shown with the current number situation) is that the user has to know whether the conversion method is on type A or type B. ie:
(Unless you code it on both, which won't always be possible).
That is where the ~ operator comes in, to focus attention on the task of conversion, not the exact mechanism/method used to achieve it.
The more interesting case is where you have two types that don't know about each other, but you want to write conversion logic. Scala's implicits allows this - arbitrary pieces of logic to convert between two types, but it is not easy. For example, Decimal has method
plusInt
, but really this represents an aspect of the combination of Int and Decimal, and probably shouldn't be a method on either.Thus, the "correct" solution is one where all aspects of the relationship between two classes is represented by a "junction" class:
Interestingly, this direction probably removes the need for @Operator.
I would point out that methods like Int#toDuration and Int#toDateTime(TimeZone) look decidedly dubious in the API right now...
Akcelisto Mon 7 Nov 2011
I want to remind about a issue "Deserialization take off short ctor". Please, fix somehow this. See #1370.