-
Notifications
You must be signed in to change notification settings - Fork 63
implicit conversions for Java types #6189
Description
I have long opposed defining any implicit type conversions in the language, because of the ambiguities and confusing behavior that can result. For example, consider what happens if we define an implicit type conversion from Java List to Ceylon MutableList, and write:
javaObject.someList.sizeThe programmer might expect size to refer to the attribute MutableList.size of type Integer, but in fact it actually refers to the method size() of List. Even worse ambiguities arise if implicit conversions are transitive.
As we've seen, however, confusion and inconvenience can also arise from not having any sort of implicit type conversions, for example, when Java's String type leaks into Ceylon code, or when strings aren't assignable to Java parameters that accept CharSequence.
Yesterday, I realized that there is an approach to implicit conversions that doesn't lead to the ambiguities I've always been bothered by. Traditional implicit type conversions are only applied when the original type is not assignable to the declared type of something. They are thus, in some sense, "lazy", and the original type always has the chance to "leak out".
But for the specific case of inter-language interop, that's actually not what we need. What if we defined some type conversions that were always applied, as soon as possible? That is, as soon as we have an expression of type java.lang.String, the conversion to ceylon.language::String is immediately applied, and no operations of java.lang.String are ever visible. Then, essentially, no Ceylon code would ever be able to interact with the type java.lang.String except to the extent that it can occur as a type argument in a generic type.
Likewise, no Ceylon code would ever interact directly with a java.util.function.Predicate<T>, since any expression of that type would immediately be converted to Boolean(T).
It seems to me that this solution is perfect for protecting Ceylon code from Java types, including String, primitive wrapper classes, @FunctionalInterfaces, Iterable, List, Set, Map, and perhaps even primitive arrays (which could be converted to Array). I think and hope it would also work for the special transformation CharSequence <-> List<Character>, which I admit is a slightly tricky case.
So the next problem that arises is how to pass a value to a Java parameter declared with one of these "hidden" types. Since we can no longer form an expression of type java.lang.Integer, we can't call any Java method that accepts a java.lang.Integer.
Well, the solution is quite similar: the typechecker, when it comes to assign a type to parameter of one of the "hidden" types, actually applies the same conversions, and arrives at a type to which we can assign!
Obviously, this implies a mathematical property of the transformation, namely that it is a bijection. Fortunately, I think we can shoehorn our transformations into a bijective function. However, I still need to verify that.
For this to really work correctly and transparently, there's one big caveat: the set of transformations and reverse transformations would need to be applied every time we assign a supertype like Object or Number to or from Java! Actually CharSequence also counts as one of these "supertypes", so it's not that special. As is Iterable I guess.
Anyway, I think this is a very strong way forward, and just requires thinking through the details. I believe it can be made to work.
WDYT?