Überladung von case-Labels #348

Open
opened 2024-09-26 08:27:34 +00:00 by dholle · 10 comments
Owner

Folgendes Beispiel:

record R(Object o) {}

f(Integer i) {}
f(Float f) {}

m(a) {
    switch(a) {
        case R(x) -> f(x);
    };
}

In diesem Fall wären zwei Möglichkeiten für den Funktionsaufruf f gegeben.
Das case könnte also Überladen werden und dann so aussehen:

switch(a) {
    case R(Integer x) -> f(x);
    case R(Float x) -> f(x):
};

Die Problematik ist nun, dass im Result-Set erstmal nicht zu sehen ist, ob die ganze Methode oder nur der Case überladen werden soll.

In der Besprechung sind wir jetzt so verblieben, dass geprüft werden muss ob sich der Typ von a ändert. Falls nein wird jeweils für alle Kombinationen der Typvariablen im Record-Pattern eine Überladung durchgeführt.

Folgendes Beispiel: ``` record R(Object o) {} f(Integer i) {} f(Float f) {} m(a) { switch(a) { case R(x) -> f(x); }; } ``` In diesem Fall wären zwei Möglichkeiten für den Funktionsaufruf `f` gegeben. Das case könnte also Überladen werden und dann so aussehen: ``` switch(a) { case R(Integer x) -> f(x); case R(Float x) -> f(x): }; ``` Die Problematik ist nun, dass im Result-Set erstmal nicht zu sehen ist, ob die ganze Methode oder nur der Case überladen werden soll. In der Besprechung sind wir jetzt so verblieben, dass geprüft werden muss ob sich der Typ von `a` ändert. Falls nein wird jeweils für alle Kombinationen der Typvariablen im Record-Pattern eine Überladung durchgeführt.
Collaborator

Nur nochmal für mich zum Verständnis:

wenn ich also den Switch-Case

switch(a) {
    case R(x) -> f(x);
};

habe, soll "automatisch" die richtige Methode dazu aufgerufen werden.
In diesem Fall bei x = Integer Case f(Integer x), bei x = Float Case f(Float x).

Nur nochmal für mich zum Verständnis: wenn ich also den Switch-Case ```java switch(a) { case R(x) -> f(x); }; ``` habe, soll "automatisch" die richtige Methode dazu aufgerufen werden. In diesem Fall bei `x = Integer` Case `f(Integer x)`, bei `x = Float` Case `f(Float x)`.
Author
Owner

Ich arbeite jetzt mit folgendem Beispiel:

import java.lang.Integer;
import java.lang.Double;
import java.lang.Object;

public record R(Object o) {}

public class OverloadSwitch {

    f(Double d) { return d; }
    f(Integer i) { return i; }

    public m(r) {
        return switch(r) {
            case R(o) -> f(o);
        };
    }
}

Allerdings gibt es ein Problem. Die Constraints müssen hier auch angepasst werden. Sonst wird nämlich eine Überladung für m erstellt, jeweils mit Double oder Integer als Return-Typ. Das Gleiche passiert auch, wenn man eine lokale Variable erstellt.

Ich bin mir nicht sicher, ob das komplett im Bytecode lösbar ist, da ich auch Änderungen am Result-Set machen müsste.

@pl Das ist so ein Fall, in dem sich der Switch auf die äußere Methode auswirkt. Hast du eine Idee wie wir das lösen können?
Eigentlich müsste das meiner Meinung nach von der Typinferenz abgedeckt werden. Könnte man hier ein Constraint einfügen, welches die Fälle (von f) wieder einsammelt und einen gemeinsamen Rückgabetyp für das Switch berechnet? Ich möchte ungern im Bytecode einen gemeinsamen Supertyp für das Switch finden.

Ich arbeite jetzt mit folgendem Beispiel: ```java import java.lang.Integer; import java.lang.Double; import java.lang.Object; public record R(Object o) {} public class OverloadSwitch { f(Double d) { return d; } f(Integer i) { return i; } public m(r) { return switch(r) { case R(o) -> f(o); }; } } ``` Allerdings gibt es ein Problem. Die Constraints müssen hier auch angepasst werden. Sonst wird nämlich eine Überladung für `m` erstellt, jeweils mit `Double` oder `Integer` als Return-Typ. Das Gleiche passiert auch, wenn man eine lokale Variable erstellt. Ich bin mir nicht sicher, ob das komplett im Bytecode lösbar ist, da ich auch Änderungen am Result-Set machen müsste. @pl Das ist so ein Fall, in dem sich der Switch auf die äußere Methode auswirkt. Hast du eine Idee wie wir das lösen können? Eigentlich müsste das meiner Meinung nach von der Typinferenz abgedeckt werden. Könnte man hier ein Constraint einfügen, welches die Fälle (von f) wieder einsammelt und einen gemeinsamen Rückgabetyp für das Switch berechnet? Ich möchte ungern im Bytecode einen gemeinsamen Supertyp für das Switch finden.
Owner

Kannst mal das ResultSet und dei Abstrakte Syntax mit Typisierung hier posten

Kannst mal das ResultSet und dei Abstrakte Syntax mit Typisierung hier posten
Author
Owner

Abstrakte Syntax:

class SwitchOverload {

SwitchOverload()({
  })::TPH BA
  TPH AC f(java.lang.Double d)({
    return (d)::java.lang.Double;
  })::TPH AD

  TPH AE f(java.lang.Integer i)({
    return (i)::java.lang.Integer;
  })::TPH AF

  TPH AG m(TPH AH r)({
    return switch((r)::TPH AH){
      case TPH AM ((this)::TPH AI.R Signature: [TPH AK, TPH AL]((o)::TPH AJ))::TPH AM:
        yield (((this)::TPH AO.f Signature: [TPH AP, TPH AQ]((o)::TPH AJ))::TPH AR)::TPH AR;

    }::TPH AV;
  })::TPH AW

  SwitchOverload()({
    super(()) Signature: [TPH AY];
  })::TPH AZ

}

ResultSet:

RESULT Final: [[(TPH AQ = java.lang.Double), (TPH AO = SwitchOverload), (TPH AJ = java.lang.Double), (TPH AR = java.lang.Double), (TPH AM, TPH AH), (TPH AU = java.lang.Double), (TPH J = R), (TPH AE = java.lang.Integer), (TPH AV = java.lang.Double), (TPH AP = java.lang.Double), (TPH AG = java.lang.Double), (TPH AC = java.lang.Double)], [(TPH AE = java.lang.Integer), (TPH AG = java.lang.Integer), (TPH AV = java.lang.Integer), (TPH J = R), (TPH AC = java.lang.Double), (TPH AP = java.lang.Integer), (TPH AU = java.lang.Integer), (TPH AQ = java.lang.Integer), (TPH AM, TPH AH), (TPH AR = java.lang.Integer), (TPH AJ = java.lang.Integer), (TPH AO = SwitchOverload)]]

Mir fällt gerade auf, dass das AI nicht definiert ist, das hat vorher noch funktioniert. Sieht so aus als ob mein pull da was geändert hat.

Abstrakte Syntax: ``` class SwitchOverload { SwitchOverload()({ })::TPH BA TPH AC f(java.lang.Double d)({ return (d)::java.lang.Double; })::TPH AD TPH AE f(java.lang.Integer i)({ return (i)::java.lang.Integer; })::TPH AF TPH AG m(TPH AH r)({ return switch((r)::TPH AH){ case TPH AM ((this)::TPH AI.R Signature: [TPH AK, TPH AL]((o)::TPH AJ))::TPH AM: yield (((this)::TPH AO.f Signature: [TPH AP, TPH AQ]((o)::TPH AJ))::TPH AR)::TPH AR; }::TPH AV; })::TPH AW SwitchOverload()({ super(()) Signature: [TPH AY]; })::TPH AZ } ``` ResultSet: ``` RESULT Final: [[(TPH AQ = java.lang.Double), (TPH AO = SwitchOverload), (TPH AJ = java.lang.Double), (TPH AR = java.lang.Double), (TPH AM, TPH AH), (TPH AU = java.lang.Double), (TPH J = R), (TPH AE = java.lang.Integer), (TPH AV = java.lang.Double), (TPH AP = java.lang.Double), (TPH AG = java.lang.Double), (TPH AC = java.lang.Double)], [(TPH AE = java.lang.Integer), (TPH AG = java.lang.Integer), (TPH AV = java.lang.Integer), (TPH J = R), (TPH AC = java.lang.Double), (TPH AP = java.lang.Integer), (TPH AU = java.lang.Integer), (TPH AQ = java.lang.Integer), (TPH AM, TPH AH), (TPH AR = java.lang.Integer), (TPH AJ = java.lang.Integer), (TPH AO = SwitchOverload)]] ``` Mir fällt gerade auf, dass das `AI` nicht definiert ist, das hat vorher noch funktioniert. Sieht so aus als ob mein pull da was geändert hat.
Owner

Hier müssten wohl zwei Methoden erzeugt werden. Wir müssen unsere Überlegungen von letzter Woche ergänzen. Nicht nur die input-Variable AH, sondern auch die output-Variable AR muss in allen Fällen des Resultssets gleich sein, um nur weitere Cases zu erzeugen.

Hier müssten wohl zwei Methoden erzeugt werden. Wir müssen unsere Überlegungen von letzter Woche ergänzen. Nicht nur die input-Variable AH, sondern auch die output-Variable AR muss in allen Fällen des Resultssets gleich sein, um nur weitere Cases zu erzeugen.
Owner

Das Ergebnis wäre dann:

import java.lang.Integer;
import java.lang.Double;
import java.lang.Object;

public record R(Object o) {}

public class OverloadSwitch {

    f(Double d) { return d; }

    f(Integer i) { return i; }

    public Double m(R r) {
        return switch(r) {
            case R(Double o) -> f(o);
        };
    }

    public Integer m(R r) {
        return switch(r) {
            case R(Integer o) -> f(o);
        };
    }
}
Das Ergebnis wäre dann: ```java import java.lang.Integer; import java.lang.Double; import java.lang.Object; public record R(Object o) {} public class OverloadSwitch { f(Double d) { return d; } f(Integer i) { return i; } public Double m(R r) { return switch(r) { case R(Double o) -> f(o); }; } public Integer m(R r) { return switch(r) { case R(Integer o) -> f(o); }; } } ```
Owner

Um einen Fall zu generieren bei den tatsächlich ein weiterer Case-Fall erzeugt würde, müsste es so aussehen:

import java.lang.Integer;
import java.lang.Double;
import java.lang.Object;

public record R(Object o) {}

public class OverloadSwitch {

f(Double d) { printDouble(d); }

f(Integer i) { printInteger(i); }

public m(r) {
    switch(r) {
        case R(o) -> f(o);
    };
}

Dann würde es zu

import java.lang.Integer;
import java.lang.Double;
import java.lang.Object;

public record R(Object o) {}

public class OverloadSwitch {

f(Double d) { printDouble(d); }

f(Integer i) { printInteger(i); }

public m(R r) {
    switch(r) {
        case R(Double o) -> f(o);
        case R(Integer o) -> f(o);
    };
}
Um einen Fall zu generieren bei den tatsächlich ein weiterer Case-Fall erzeugt würde, müsste es so aussehen: ```java import java.lang.Integer; import java.lang.Double; import java.lang.Object; public record R(Object o) {} public class OverloadSwitch { f(Double d) { printDouble(d); } f(Integer i) { printInteger(i); } public m(r) { switch(r) { case R(o) -> f(o); }; } ``` Dann würde es zu ```java import java.lang.Integer; import java.lang.Double; import java.lang.Object; public record R(Object o) {} public class OverloadSwitch { f(Double d) { printDouble(d); } f(Integer i) { printInteger(i); } public m(R r) { switch(r) { case R(Double o) -> f(o); case R(Integer o) -> f(o); }; } ```
Author
Owner

Ich hab ein kleines Problem... Für die überladenen cases wähle ich ein Result-Set aus, in dem die Typen welche im Pattern benutzt werden gleich sind.

Das funktioniert soweit ganz gut, aber was passiert wenn die Methode gleichzeitig auch noch überladen wird
Dann kann es ja sein, dass mehrere Result-Sets in frage kommen.

Ich habe auch versucht den Typ einfach zu überschreiben, aber zu dem Zeitpunkt sind die Relationen nicht mehr klar.
Wenn z.B A = B und A = Integer dann steht bei mir nur noch A = Integer und B = Integer drin. Wenn ich aber jetzt sage, dass A = Double, dann wird B nicht geändert.

Ich hab ein kleines Problem... Für die überladenen cases wähle ich ein Result-Set aus, in dem die Typen welche im Pattern benutzt werden gleich sind. Das funktioniert soweit ganz gut, aber was passiert wenn die Methode gleichzeitig auch noch überladen wird Dann kann es ja sein, dass mehrere Result-Sets in frage kommen. Ich habe auch versucht den Typ einfach zu überschreiben, aber zu dem Zeitpunkt sind die Relationen nicht mehr klar. Wenn z.B `A = B` und `A = Integer` dann steht bei mir nur noch `A = Integer` und `B = Integer` drin. Wenn ich aber jetzt sage, dass `A = Double`, dann wird `B` nicht geändert.
Owner

Verstehe ich so leider nicht. Kannst Du bitte noch ein Beipiel dafür angeben.

Verstehe ich so leider nicht. Kannst Du bitte noch ein Beipiel dafür angeben.
Author
Owner

Das Problem tritt hier auf:

import java.lang.Integer;
import java.lang.Double;
import java.lang.Number;

public record R(Number n) {}

public class SwitchOverload {

    Number f(Double d) { return d * 2; }
    Number f(Integer i) { return i * 5; }

    public m(r, x) {
        x = x + x;
        return switch(r) {
            case R(o) -> {
                x = x + x;
                yield f(o);
            }
        };
    }
}

Jetzt werden sowohl die Methode als auch der Case überladen. Für den überladenen Case muss ich eins der Result-Sets auswählen. Nun gibt es allerdings zwei die in Frage kommen könnten. Es reicht also nicht aus, nur den Typ von o anzuschauen.

Die Result-Sets sehen ungefär so aus:

-> [x = Double, o = Double],
-> [x = Integer, o = Double],
[x = Double, o = Integer],
[x = Integer, o = Integer]

Für o = Double gibt es also zwei Result-Sets. Richtig wäre das Result-Set, welches mit der Überladung der äußeren Methode übereinstimmt.

Praktisch wäre sowas wie eine Hierarchie in den Result-Sets, aber keine Ahnung wie das umgesetzt werden soll.

Das Problem tritt hier auf: ```java import java.lang.Integer; import java.lang.Double; import java.lang.Number; public record R(Number n) {} public class SwitchOverload { Number f(Double d) { return d * 2; } Number f(Integer i) { return i * 5; } public m(r, x) { x = x + x; return switch(r) { case R(o) -> { x = x + x; yield f(o); } }; } } ``` Jetzt werden sowohl die Methode als auch der Case überladen. Für den überladenen Case muss ich eins der Result-Sets auswählen. Nun gibt es allerdings zwei die in Frage kommen könnten. Es reicht also nicht aus, nur den Typ von `o` anzuschauen. Die Result-Sets sehen ungefär so aus: ``` -> [x = Double, o = Double], -> [x = Integer, o = Double], [x = Double, o = Integer], [x = Integer, o = Integer] ``` Für `o = Double` gibt es also zwei Result-Sets. Richtig wäre das Result-Set, welches mit der Überladung der äußeren Methode übereinstimmt. Praktisch wäre sowas wie eine Hierarchie in den Result-Sets, aber keine Ahnung wie das umgesetzt werden soll.
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#348
No description provided.