Totality over generics
forax at univ-mlv.fr
forax at univ-mlv.fr
Sat Sep 25 07:19:11 UTC 2021
----- Original Message -----
> From: "Per Minborg" <minborg at speedment.com>
> To: "Remi Forax" <forax at univ-mlv.fr>
> Cc: "amber-dev" <amber-dev at openjdk.java.net>
> Sent: Vendredi 24 Septembre 2021 20:41:34
> Subject: Re: Totality over generics
> 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.
Boxing aka adding another level of indirection is always a solution, but providing a runtime solution that changes the shape of objects for a typing issue has usually a non trivial interoperability cost with features that inspect the shape of an object like subtyping or reflection.
How to make Integer | String | LocalData a subtype of Integer | String ?
And there is also the usual issue of Monads/Boxes as return type acting as colors for functions [1].
>
> Best, Per
regards,
Rémi
[1] https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/
>
> 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