Falscher Target Type für Lambdas #325

Closed
opened 2024-04-25 18:51:12 +00:00 by i21023 · 3 comments
Collaborator

Ich glaube die Typinferenz funktioniert mit Lambda Ausdrücken, die ein bestimmtes funktionales Interface als Target Type benötigen nur lokal.

Beispiel

import java.lang.Integer;
import java.util.function.Function;
import java.util.List;
import java.util.ArrayList;
import java.util.stream.Stream;

public class Foo {
    main(){
      List<Integer> list = new ArrayList<>(List.of(1,2,3,4,5));
      var func = x -> x*2;
      return list.stream().map(func).toList();
   }
}

Dieser Code kompiliert zwar, er inferiert aber den FunN Typ anstatt java.util.function.Function für die Lambda Expression.
Zur Laufzeit gibt es entsprechend einen Fehler weil java.util.stream.Stream.map Function als Parameter erwartet.

Im Bytecode steht also folgendes:
46: invokedynamic #56, 0 // InvokeDynamic #0:apply:(LFoo;)LFun1$$Ljava$lang$Integer$_$Ljava$lang$Integer$_$;

...statt

50: invokedynamic #60, 0 // InvokeDynamic #0:apply:(LFoo;)Ljava/util/function/Function;

Zweiteres wird korrekt generiert, wenn die Lambda Expression direkt dem der Methode map übergeben wird, also:

import java.lang.Integer;
import java.util.function.Function;
import java.util.List;
import java.util.ArrayList;
import java.util.stream.Stream;

public class Foo {
    main(){
      List<Integer> list = new ArrayList<>(List.of(1,2,3,4,5));
      return list.stream().map(x -> x*2).toList();
   }
}

Der Typinferenz Algorithmus sollte ja eigentlich in der Lage sein dies zu erkennen.

Auch im Paper von 2017 wurde in Kapitel 7 schon ein Beispiel mit dem gleichen Prinzip wie oben gegeben:

Fun1*<ActionEvent, String> helloworld = event -> System.out.println("Hello␣World!"); 
Button btn = new Button(); 
btn.setText (" Say ␣ ' Hello ␣ World '" ); 
btn.setOnAction(helloworld);
Ich glaube die Typinferenz funktioniert mit Lambda Ausdrücken, die ein bestimmtes funktionales Interface als Target Type benötigen nur lokal. ### Beispiel ```java import java.lang.Integer; import java.util.function.Function; import java.util.List; import java.util.ArrayList; import java.util.stream.Stream; public class Foo { main(){ List<Integer> list = new ArrayList<>(List.of(1,2,3,4,5)); var func = x -> x*2; return list.stream().map(func).toList(); } } ``` Dieser Code kompiliert zwar, er inferiert aber den FunN Typ anstatt `java.util.function.Function` für die Lambda Expression. Zur Laufzeit gibt es entsprechend einen Fehler weil `java.util.stream.Stream.map` `Function` als Parameter erwartet. Im Bytecode steht also folgendes: `46: invokedynamic #56, 0 // InvokeDynamic #0:apply:(LFoo;)LFun1$$Ljava$lang$Integer$_$Ljava$lang$Integer$_$;` ...statt `50: invokedynamic #60, 0 // InvokeDynamic #0:apply:(LFoo;)Ljava/util/function/Function;` Zweiteres wird korrekt generiert, wenn die Lambda Expression direkt dem der Methode map übergeben wird, also: ```java import java.lang.Integer; import java.util.function.Function; import java.util.List; import java.util.ArrayList; import java.util.stream.Stream; public class Foo { main(){ List<Integer> list = new ArrayList<>(List.of(1,2,3,4,5)); return list.stream().map(x -> x*2).toList(); } } ``` Der Typinferenz Algorithmus sollte ja eigentlich in der Lage sein dies zu erkennen. Auch im Paper von 2017 wurde in Kapitel 7 schon ein Beispiel mit dem gleichen Prinzip wie oben gegeben: ```java Fun1*<ActionEvent, String> helloworld = event -> System.out.println("Hello␣World!"); Button btn = new Button(); btn.setText (" Say ␣ ' Hello ␣ World '" ); btn.setOnAction(helloworld); ```
Owner

@pl Wir haben heute schon drüber gesprochen aber ich glaube es gibt doch noch einen Fehler.
Der contextType, also der aus der Signatur macht nur dann Sinn, wenn das Lambda inline definiert wird.
Was hier passiert ist, dass die lokale Variable func schon den falschen Typ hat (Fun1$$) und ich an der Stelle wo der Methodenaufruf stattfindet das Lambda ja bereits generiert habe. Ich könnte es höchstens noch casten aber das funktioniert ja auch nicht.
TPH AF muss also schon auf java.util.function.Function zeigen, sonst funktioniert es nicht. Sowieso ist dieser contextType den ich hier eingeführt habe etwas seltsam, da ja eigentlich die Typinferenz schon die richtigen Typen liefern sollte.

@pl Wir haben heute schon drüber gesprochen aber ich glaube es gibt doch noch einen Fehler. Der `contextType`, also der aus der Signatur macht nur dann Sinn, wenn das Lambda inline definiert wird. Was hier passiert ist, dass die lokale Variable `func` schon den falschen Typ hat (`Fun1$$`) und ich an der Stelle wo der Methodenaufruf stattfindet das Lambda ja bereits generiert habe. Ich könnte es höchstens noch casten aber das funktioniert ja auch nicht. `TPH AF` muss also schon auf `java.util.function.Function` zeigen, sonst funktioniert es nicht. Sowieso ist dieser `contextType` den ich hier eingeführt habe etwas seltsam, da ja eigentlich die Typinferenz schon die richtigen Typen liefern sollte.
Owner

Das wird leider nicht klappen. Der Typ von func muss Fun1$$ sein. Was machts Du wenn das Programm wie folgt erweitert wird:

import java.lang.Integer;
import java.util.function.Function;
import java.util.List;
import java.util.ArrayList;
import java.util.stream.Stream;

_interface myFun<A, B> extends Function<A,B> { }_

public class Foo {
    main(){
      List<Integer> list = new ArrayList<>(List.of(1,2,3,4,5));
      var func = x -> x*2;
      _myFun<Integer, Integer> func2 = func;_
      return list.stream().map(func).toList();
   }
}
Das wird leider nicht klappen. Der Typ von `func` muss `Fun1$$` sein. Was machts Du wenn das Programm wie folgt erweitert wird: ``` import java.lang.Integer; import java.util.function.Function; import java.util.List; import java.util.ArrayList; import java.util.stream.Stream; _interface myFun<A, B> extends Function<A,B> { }_ public class Foo { main(){ List<Integer> list = new ArrayList<>(List.of(1,2,3,4,5)); var func = x -> x*2; _myFun<Integer, Integer> func2 = func;_ return list.stream().map(func).toList(); } } ```
Owner

Es wurde eine Lösung implementiert, das Beispiel funktioniert jetzt.
Was passiert ist, dass jeweils wenn ein Lambda in einen anderen Typ überführt wird, wird eine Art Wrapperklasse erzeugt, welche den anderen Typ implementiert. Diese macht nichts anderes als die orginale Funktion auszuführen.

Es wurde eine Lösung implementiert, das Beispiel funktioniert jetzt. Was passiert ist, dass jeweils wenn ein Lambda in einen anderen Typ überführt wird, wird eine Art Wrapperklasse erzeugt, welche den anderen Typ implementiert. Diese macht nichts anderes als die orginale Funktion auszuführen.
Sign in to join this conversation.
No Milestone
No project
No Assignees
3 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: JavaTX/JavaCompilerCore#325
No description provided.