Totality over generics
Per Minborg
minborg at speedment.com
Fri Sep 24 18:41:34 UTC 2021
Thanks for the elaboration Rémi.
As a closing note, I provide a "wrapper" solution with surprisingly
little overhead that works in Java 17. Perhaps this can spark some ideas
for people looking for union type wrappers in combination with switch
totality or future desugaring solutions. The concept scales O(N) over
participating types.
sealed interface IntegerStringUnion {
record Int(Integer get) implements IntegerStringUnion {}
record Str(String get) implements IntegerStringUnion {}
}
int eval(IntegerStringUnion t) {
return switch (t) {
case Int i -> i.get();
case Str s -> Integer.parseInt(s.get());
case null -> 0;
// totality
};
}
Real solutions should assert non-nullability invariants in the records'
constructors.
Best, Per
On 9/24/21 6:58 PM, Remi Forax wrote:
> ----- Original Message -----
>> From: "Per Minborg" <minborg at speedment.com>
>> To: "amber-dev" <amber-dev at openjdk.java.net>
>> Sent: Vendredi 24 Septembre 2021 18:19:40
>> Subject: Totality over generics
>> Hi,
> Hi Per,
>
>> What if we could do something like this in some distant future:
>>
>> <T extends Integer | String> int eval(T t) {
>> return switch (t) {
>> case Integer i -> i;
>> case String s -> Integer.parseInt(s);
>> case null -> 0;
>> // Totality
>> };
>> }
>>
>>
>> Already today , an equivalent second layer of sealed wrapper classes
>> could achieve the same goal but with much more ceremony. Of course,
>> there are also overloads but how cool is that?
> The problem is that this is not compatible with how a OR between types works in Java,
> By example, if you use ?: between a String and an Integer, you get the common super types between String and Integer, something like Object & Comparable<? extends Comparable<?>> & Serializable but not String | Integer.
>
> So a code like this does not compile
> Integer i = 3;
> String s = "foo";
> Integer | String value = (condition)? i: s; // oops
>
> You can say that this is a bug with ?:, but a method like <T> T choose(boolean cond, T value1, T value2) will infer T the same way.
>
> So what you are proposing is cool but we can not doing it without breaking a lot of existing codes.
>
> The reason it was done that way is that if String and Integer both have a method with the same signature, there was no way in the bytecode to call that method apart if the method was declared one of the common super type. So instead of allowing the type String | Integer but not allow to call all methods on it, it was decided to replace String | Integer by the common supertypes because with these types we can generate the correct bytecode.
>
> Since Java 7, we can now use the bytecode instruction invokedynamic to dynamically call the method of Integer or the method of String,
> so this is now technically possible to implement String | Integer on the JVM.
>
> Scala 3 introduce the concept of | between types, this is one of the reasons why Scala 3 is not compatible with Scala 2 (Scala 2 like Java uses the common supertypes).
>
> Maybe someone may come in the future with a way to represent String | Integer which is backward compatible in Java. One idea is to try to keep String | Integer but convert it to the common supertypes if necessary. But it's research work that as far as i know has never been done.
>
>> Best, Per Minborg
> regards,
> Rémi
More information about the amber-dev
mailing list