Right now there is no automatic coercion, casting, conversion between number types (Int, Float, Decimal):
Int x := 6
2.0 + x // cannot do this
2.0 + x.toDecimal // can do this
This is deliberate because I wanted to keep it "in your face" what you are doing. In Java a primitive cast between int and float is an efficient machine instruction. In Fan it typically requires allocating a new object.
This is the biggest tradeoff we've made in Fan - we sacrifice numeric performance to keep things pure OO (which has lots of benefits). I intern a big range of Ints, and there is huge opportunity to optimize this stuff with escape analysis, etc. But regardless there is a real cost associated with switching between Int, Float, and Decimal. So I want to keep it very visible in the code what is happening but avoiding any implicit coercions.
So that is the philosophy of why it is designed the way it is, but I'd love to alternative view points.
JohnDGWed 18 Jun 2008
I prefer hiding implementation details, because the implementation is increasingly likely to change, while the syntax of the language is decreasingly likely to change.
If Fan becomes popular, the compiler will likely convert to primitives when it can (for example, local variables), nullifying any performance issues.
Also, without automatic conversion, you're pretty much forced to always declare types (or else everyone will always cast everything because they won't be sure what kind of number a variable is holding).
heliumWed 18 Jun 2008
I don't see how this how this showing implementation details. Fan has no implicit coercion. Many languages have good optimising compilers and no implicit coercion. To me it sounds like a design decision ("This is deliberate because I wanted to keep it "in your face" what you are doing"). I like that design.
JohnDGWed 18 Jun 2008
Yes, but the reason he wants to keep it "in your face" is because it has a performance penalty in the current implementation. That's not a reason to choose one syntax over another.
Good language decisions are completely independent of the details of the current implementation -- because as I said before, the implementation is guaranteed to change, in degree and proportion to the adoption of Fan, while the syntax is less likely to change with every passing year.
tompalmerWed 18 Jun 2008
I'm also super against implicit conversions. One of the things I dislike about C# and Scala (and Java for that matter, when it comes to primitives).
I'm not proposing implicit conversion. I'm proposing compile-time overloading of operators, understanding that overloading of slot names is a big no no. Just as with my getAtRange example on the other thread, naming conventions could allow slots like this:
Decimal plusInt(Int i) {
return this + i.toDecimal
}
The slot name is plusInt, but the compiler allows the overloading of the symbol + knowing to choose the matching slot (based on type and/or naming convention).
I think the slice vs. get example shows that you've already had this need to overload operators. (It was also an issue in ECMAScript 4 conversations which is why they headed into multimethods and now seem to be unraveling the whole topic.) My recommendation here is just a way to standardize the handling of operator overloading in a consistent way (and simpler than the fun they were having with ES4, in my opinion).
JohnDGWed 18 Jun 2008
How about this: automatic conversions to the type of the LHS when the appropriate toXXX method is defined. For example, if Int has toDecimal, then Int can automatically be converted to Decimal in all assignments to a Decimal type.
Then this:
Int x := 6
Decimal result = 2.0 + x
becomes equivalent to this:
Int x := 6
Decimal result = 2.0 + x.toDecimal
This is a simple, consistent rule, which requires no hacks for primitives and which would work equally well for user-defined types. And the mental burden is low because you know the target type of each implicit conversion: it's the type of the LHS.
heliumWed 18 Jun 2008
So you suggest:
foo := 1 + 1.0 // foo has type Int
bar := 1.0 + 1 // bar has type Decimal
Sorry, but no I don't like that. That will cause a lot of confusion in an a bit more compicated formula.
JohnDGWed 18 Jun 2008
No, I don't suggest that -- it would depend on the type of foo and bar, respectively, and if the types were undeclared, the expression would be illegal without casting.
tompalmerThu 19 Jun 2008
Helium is right, since 1 is Int and 1.0 is Decimal. After thinking through all this, I think the current behavior is best. No need to multi-operator-overloading by the compiler (except for slice) and no implicit type conversion. That's my opinion right now.
JohnDGThu 19 Jun 2008
No, he's not right. Neither 1 nor 1.0 is the left-hand side of the equation. foo and bar are. As far as I can see, there are two cases (parameters and LHSs) where the aforementioned automatic casting would be both useful and relatively trivial.
Not just for integers, but for things like strings. Instead of writing:
a.toString + b.toString + c.toString
for a string LHS or parameter, you just write:
a + b + c
The drawback to implicit type conversion is that developer's don't know what's going on because they don't know all the rules. If you have a single rule and it's simple, and going to increase productivity, then it can actually be a help, not just a hindrance.
tompalmerThu 19 Jun 2008
Oh, I see what you mean. I'll have to think about it before I have an opinion. Thanks for explaining again.
brianThu 19 Jun 2008
I'm still like the current design - I like that Fan keeps everything very visible and doesn't do much under the covers.
Plus this is one of those cases where we can add implicit conversions later, but if we add them now we can't change our minds and remove them later.
tompalmerThu 19 Jun 2008
Personally, I also still agree with keeping as is.
Which one is clear and which is cluttered? Or, if you don't like numbers, take the string example I mentioned earlier.
The concern of keeping everything very visible is important and often overlooked in language design. But so also is the concern of easing the user's burden.
Implicit conversions and overloading have a justly deserved bad reputation because the rules are so complicated that developer's don't remember them. Not because they're inherently evil by themselves.
The above idea I suggested is loosely based on Java's behavior with toString (although many other languages have similar ideas, such as JavaScript). The compiler will automatically invoke an object's toString method if an instance of the object occurs as part of a string concatenation expression. It's nice because it's a simple rule (easy to remember), and it saves a lot of needless typing, making the code easier to understand.
But why hardcode the behavior for toString when you can be more consistent about it, and handle the more general case of toXXX, which denotes a (possibly user-defined) conversion into type XXX? This would simplify a lot of code. For example, in windowing toolkits, many algorithms require the boundaries of widgets. Instead of explicitly retrieving bounds to operate on them, the widgets could supply a toBounds method of type Rect. This would enable you to write expressions such as, Rect union = widget1.union(widget2). Thereby introducing the notion that if you can convert one type to another type (via some toXXX method), then in expressions that expect an object of the target type, you can treat the object as if it were that type (in expressions where the target type is not known, this would not be legal).
On the other hand, perhaps there are implications I'm not seeing.
brianThu 19 Jun 2008
John, couple comments:
String concat in Fan isn't really a special because Str.plus(Obj) takes any Obj as its parameter. So it isn't the same as Java which implicits adds the toString() call.
This example wouldn't really work because the original call has to be typed (Rect, Rect):
// wouldn't really work because there isn't a Widget.union
Rect union = widget1.union(widget2)
// would really be something like this
union := widget.toBounds.union(widget)
// but, then this one still seems clearer
union := widget1.bounds.union(widget2.bounds)
I've written a lot of Fan code including some numerical stuff, and I haven't found the explicit conversion to be too bad - in large part this is because you use method chaining not casting. For example:
// you really wouldn't write code like this (because we aren't casting)
(Decimal)(index - startIndex) / (Decimal)(endIndex - startIndex) * delta + start
// it would be written like this
(index - startIndex).toDecimal / (endIndex - startIndex).toDecimal * delta + start
andyThu 19 Jun 2008
The string case is alittle different because the compiler will optimize that use a StringBuffer, and can optimize certain x.toString() calls. But you can't optimize the x.toDecimal case - a new object will get created. I think thats important if people don't realize that and attempt to do a bunch of conversions in a tight loop. Comparing Fan to Java:
Yeah, the Fan version is a bit more verbose, but (1) its easier to type w/o all those parens and casts, and (2) its easier to read for the same reasons.
But I'll go back to the primary reason against making this change, as Brian mentioned, we can always go back and add this, but we once we add it we can't take it away. So lets keep it an open issue, and see how it plays out.
JohnDGFri 20 Jun 2008
Hi Brian,
Actually, I was suggesting the compiler automatically invoke relevant toXXX calls when the type of the RHS is wrong, but after sketching the algorithm for this, I see that the solution (at least my naive one) is computationally intensive for large expressions, albeit in a deterministic way.
tompalmerFri 20 Jun 2008
Normal implicit conversion in C# and Scala (and C++ or others?) waits until the final expression to do the conversion. So that's somewhat simpler, though less cool, than what you describe. But even the simpler form causes too much unexpected behavior, in my opinion. I'm against implicit conversion. (I could go into my opinion on subsets, implicit type hierarchies, pointer/value equivalence, and other such ways to make it work OK when done right, but that's much more complicated than standard inheritance and not worth the effort, in my opinion. The effect would be similar to the handling of primitive coercion in Java, but generalizable. But it's just not worth it, methinks.)
brian Wed 18 Jun 2008
Break out from Operators to Methods.
Right now there is no automatic coercion, casting, conversion between number types (Int, Float, Decimal):
This is deliberate because I wanted to keep it "in your face" what you are doing. In Java a primitive cast between
int
andfloat
is an efficient machine instruction. In Fan it typically requires allocating a new object.This is the biggest tradeoff we've made in Fan - we sacrifice numeric performance to keep things pure OO (which has lots of benefits). I intern a big range of Ints, and there is huge opportunity to optimize this stuff with escape analysis, etc. But regardless there is a real cost associated with switching between Int, Float, and Decimal. So I want to keep it very visible in the code what is happening but avoiding any implicit coercions.
So that is the philosophy of why it is designed the way it is, but I'd love to alternative view points.
JohnDG Wed 18 Jun 2008
I prefer hiding implementation details, because the implementation is increasingly likely to change, while the syntax of the language is decreasingly likely to change.
If Fan becomes popular, the compiler will likely convert to primitives when it can (for example, local variables), nullifying any performance issues.
Also, without automatic conversion, you're pretty much forced to always declare types (or else everyone will always cast everything because they won't be sure what kind of number a variable is holding).
helium Wed 18 Jun 2008
I don't see how this how this showing implementation details. Fan has no implicit coercion. Many languages have good optimising compilers and no implicit coercion. To me it sounds like a design decision ("This is deliberate because I wanted to keep it "in your face" what you are doing"). I like that design.
JohnDG Wed 18 Jun 2008
Yes, but the reason he wants to keep it "in your face" is because it has a performance penalty in the current implementation. That's not a reason to choose one syntax over another.
Good language decisions are completely independent of the details of the current implementation -- because as I said before, the implementation is guaranteed to change, in degree and proportion to the adoption of Fan, while the syntax is less likely to change with every passing year.
tompalmer Wed 18 Jun 2008
I'm also super against implicit conversions. One of the things I dislike about C# and Scala (and Java for that matter, when it comes to primitives).
I'm not proposing implicit conversion. I'm proposing compile-time overloading of operators, understanding that overloading of slot names is a big no no. Just as with my
getAtRange
example on the other thread, naming conventions could allow slots like this:The slot name is
plusInt
, but the compiler allows the overloading of the symbol+
knowing to choose the matching slot (based on type and/or naming convention).I think the
slice
vs.get
example shows that you've already had this need to overload operators. (It was also an issue in ECMAScript 4 conversations which is why they headed into multimethods and now seem to be unraveling the whole topic.) My recommendation here is just a way to standardize the handling of operator overloading in a consistent way (and simpler than the fun they were having with ES4, in my opinion).JohnDG Wed 18 Jun 2008
How about this: automatic conversions to the type of the LHS when the appropriate toXXX method is defined. For example, if Int has toDecimal, then Int can automatically be converted to Decimal in all assignments to a Decimal type.
Then this:
becomes equivalent to this:
This is a simple, consistent rule, which requires no hacks for primitives and which would work equally well for user-defined types. And the mental burden is low because you know the target type of each implicit conversion: it's the type of the LHS.
helium Wed 18 Jun 2008
So you suggest:
Sorry, but no I don't like that. That will cause a lot of confusion in an a bit more compicated formula.
JohnDG Wed 18 Jun 2008
No, I don't suggest that -- it would depend on the type of
foo
andbar
, respectively, and if the types were undeclared, the expression would be illegal without casting.tompalmer Thu 19 Jun 2008
Helium is right, since 1 is Int and 1.0 is Decimal. After thinking through all this, I think the current behavior is best. No need to multi-operator-overloading by the compiler (except for
slice
) and no implicit type conversion. That's my opinion right now.JohnDG Thu 19 Jun 2008
No, he's not right. Neither 1 nor 1.0 is the left-hand side of the equation.
foo
andbar
are. As far as I can see, there are two cases (parameters and LHSs) where the aforementioned automatic casting would be both useful and relatively trivial.Not just for integers, but for things like strings. Instead of writing:
for a string LHS or parameter, you just write:
The drawback to implicit type conversion is that developer's don't know what's going on because they don't know all the rules. If you have a single rule and it's simple, and going to increase productivity, then it can actually be a help, not just a hindrance.
tompalmer Thu 19 Jun 2008
Oh, I see what you mean. I'll have to think about it before I have an opinion. Thanks for explaining again.
brian Thu 19 Jun 2008
I'm still like the current design - I like that Fan keeps everything very visible and doesn't do much under the covers.
Plus this is one of those cases where we can add implicit conversions later, but if we add them now we can't change our minds and remove them later.
tompalmer Thu 19 Jun 2008
Personally, I also still agree with keeping as is.
JohnDG Thu 19 Jun 2008
Contrast a random line of code with casting:
to that same line of code without casting:
Which one is clear and which is cluttered? Or, if you don't like numbers, take the string example I mentioned earlier.
The concern of keeping everything very visible is important and often overlooked in language design. But so also is the concern of easing the user's burden.
Implicit conversions and overloading have a justly deserved bad reputation because the rules are so complicated that developer's don't remember them. Not because they're inherently evil by themselves.
The above idea I suggested is loosely based on Java's behavior with
toString
(although many other languages have similar ideas, such as JavaScript). The compiler will automatically invoke an object'stoString
method if an instance of the object occurs as part of a string concatenation expression. It's nice because it's a simple rule (easy to remember), and it saves a lot of needless typing, making the code easier to understand.But why hardcode the behavior for
toString
when you can be more consistent about it, and handle the more general case oftoXXX
, which denotes a (possibly user-defined) conversion into typeXXX
? This would simplify a lot of code. For example, in windowing toolkits, many algorithms require the boundaries of widgets. Instead of explicitly retrieving bounds to operate on them, the widgets could supply a toBounds method of typeRect
. This would enable you to write expressions such as,Rect union = widget1.union(widget2)
. Thereby introducing the notion that if you can convert one type to another type (via some toXXX method), then in expressions that expect an object of the target type, you can treat the object as if it were that type (in expressions where the target type is not known, this would not be legal).On the other hand, perhaps there are implications I'm not seeing.
brian Thu 19 Jun 2008
John, couple comments:
String concat in Fan isn't really a special because Str.plus(Obj) takes any Obj as its parameter. So it isn't the same as Java which implicits adds the toString() call.
This example wouldn't really work because the original call has to be typed (Rect, Rect):
I've written a lot of Fan code including some numerical stuff, and I haven't found the explicit conversion to be too bad - in large part this is because you use method chaining not casting. For example:
andy Thu 19 Jun 2008
The string case is alittle different because the compiler will optimize that use a StringBuffer, and can optimize certain x.toString() calls. But you can't optimize the x.toDecimal case - a new object will get created. I think thats important if people don't realize that and attempt to do a bunch of conversions in a tight loop. Comparing Fan to Java:
Yeah, the Fan version is a bit more verbose, but (1) its easier to type w/o all those parens and casts, and (2) its easier to read for the same reasons.
But I'll go back to the primary reason against making this change, as Brian mentioned, we can always go back and add this, but we once we add it we can't take it away. So lets keep it an open issue, and see how it plays out.
JohnDG Fri 20 Jun 2008
Hi Brian,
Actually, I was suggesting the compiler automatically invoke relevant toXXX calls when the type of the RHS is wrong, but after sketching the algorithm for this, I see that the solution (at least my naive one) is computationally intensive for large expressions, albeit in a deterministic way.
tompalmer Fri 20 Jun 2008
Normal implicit conversion in C# and Scala (and C++ or others?) waits until the final expression to do the conversion. So that's somewhat simpler, though less cool, than what you describe. But even the simpler form causes too much unexpected behavior, in my opinion. I'm against implicit conversion. (I could go into my opinion on subsets, implicit type hierarchies, pointer/value equivalence, and other such ways to make it work OK when done right, but that's much more complicated than standard inheritance and not worth the effort, in my opinion. The effect would be similar to the handling of primitive coercion in Java, but generalizable. But it's just not worth it, methinks.)