2009-05-18 5 views
28

nehme ich die folgende Klasse habe:Higher-kinded Generika in Java

public class FixExpr { 
    Expr<FixExpr> in; 
} 

Jetzt mag ich ein generisches Argument einzuführen, die Verwendung von Expr abstrahiert über:

public class Fix<F> { 
    F<Fix<F>> in; 
} 

Aber Eclipse-doesn‘ t wie folgt:

Der Typ F ist nicht generisch; es kann nicht < Fix <F> >

Ist das überhaupt oder habe ich etwas übersehen, dass diese bestimmte Instanz verursacht zu brechen parametrisiert mit Argumenten?

Einige Hintergrundinformationen: in Haskell ist dies eine allgemeine Möglichkeit, allgemeine Funktionen zu schreiben; Ich versuche, dies auf Java zu portieren. Das Typargument F im obigen Beispiel hat die Art * -> * anstelle der üblichen Art *. In Haskell sieht es dies wie:

newtype Fix f = In { out :: f (Fix f) } 
+1

Was ist das eigentliche Problem, das Sie versuchen, damit zu lösen? Kann es nicht einfacher mit einer Vorlage gelöst werden? – KitsuneYMG

Antwort

23

denke ich, was Sie versuchen, einfach zu tun, ist nicht unterstützt von Java-Generika. Der einfachere Fall

public class Foo<T> { 
    public T<String> bar() { return null; } 
} 

kompiliert auch nicht mit javac.

Da Java zur Kompilierzeit nicht weiß, was T ist, kann es nicht garantieren, dass T<String> überhaupt sinnvoll ist.Wenn Sie zum Beispiel eine Foo<BufferedImage> erstellt hätte bar die Signatur

public BufferedImage<String> bar() 

, die unsinnig ist. Da es keinen Mechanismus gibt, der Sie dazu zwingt, nur Foo s mit generischen T s zu instanziieren, weigert es sich zu kompilieren.

5

Um einen Typ Parameter zu übergeben, hat die Typdefinition zu erklären, dass es ein akzeptiert (es hat generic sein). Anscheinend ist Ihr F kein generischer Typ.

UPDATE: Die Linie

F<Fix<F>> in; 

eine Variable des Typs erklärt F, die einen Typ Parameter akzeptiert, deren Wert Fix ist, der selbst einen Typparameter akzeptiert, wird der Wert davon F. F ist in Ihrem Beispiel nicht einmal definiert. Ich glaube, Sie

wollen
Fix<F> in; 

können, dass Sie geben eine Variable vom Typ Fix (der Typ, den Sie in Ihrem Beispiel haben definieren), zu dem Sie F einen Typparameter mit dem Wert übergeben. Da Fix definiert ist, einen Typparameter zu akzeptieren, funktioniert dies.

AKTUALISIERUNG 2: Lesen Sie Ihren Titel neu, und jetzt denke ich, dass Sie versuchen könnten, etwas zu tun, das dem in "Towards Equal Rights for Higher-Kinded Types" (PDF alert) dargestellten Ansatz ähnlich ist. Wenn ja, unterstützt Java das nicht, aber Sie könnten Scala ausprobieren.

+0

Können Sie bitte ein wenig ausarbeiten? Ich habe den Typ deklariert, um einen Typparameter zu akzeptieren - Fix . Oder meinst du das nicht? – Martijn

+0

Aber der Typ, den Sie ausgewählt haben - > - ist nicht generisch; es funktioniert nur für Fix - Sie sollten es als Typ , sicher erklären? – sanbikinoraion

+0

Das Papier ist interessant - danke! – Martijn

0

Es sieht aus, als ob man so etwas wollen kann: (. Siehe die Enum-Klasse und Fragen über seine Generika)

public class Fix<F extends Fix<F>> { 
    private F in; 
} 

+0

Hi Tom, diese Lösung sieht wirklich interessant aus, aber ich verstehe noch nicht wirklich, was sie tut. Bedeutet das nicht eine gewisse Hierarchie für F? Ich möchte das nicht tun - alles, was ich von F erwarte, ist, dass es immer noch ein Typargument erhält, um vollständig zu sein. – Martijn

+0

Wenn das der Fall ist, dann ist es in Java nicht möglich zu tun, was Sie wollen - die Sprache ist nicht ausdrucksvoll genug. – Chii

+0

Es sagt, dass F eine Art von Fix ist. Da Fix generisch ist, muss das korrekte generische Argument angegeben werden. Deine Frage ist extrem abstrakt. Ich verstehe, dass Haskell ein kompliziertes Typsystem hat, das sich von Java unterscheidet. Es ist nicht verwunderlich, dass es für jedes Feature keine 1: 1-Karte gibt. –

24

Vielleicht können Sie Scala ausprobieren, eine funktionale Sprache, die auf JVM läuft und höherklassige Generika unterstützt.


[EDIT von Rahul G]

Hier ist, wie Ihr spezielles Beispiel etwa Scala übersetzt:

trait Expr[+A] 

trait FixExpr { 
    val in: Expr[FixExpr] 
} 

trait Fix[F[_]] { 
    val in: F[Fix[F]] 
} 
1

Dennoch gibt es Möglichkeiten, higer-kinded Generika in Java zu kodieren. Bitte sehen Sie sich higher-kinded-java project an.

Mit dieser als Bibliothek, können Sie Ihren Code wie folgt ändern:

public class Fix<F extends Type.Constructor> { 
    Type.App<F, Fix<F>> in; 
} 

Sie sollten wahrscheinlich

@GenerateTypeConstructor 
public class Expr<S> { 
    // ... 
} 

Diese Anmerkung erzeugt ExprTypeConstructor Klasse eine @GenerateTypeConstructor Anmerkung zu Ihrem Expr Klasse hinzuzufügen. Jetzt können Sie Ihre Fix von Expr wie folgt verarbeiten:

class Main { 
    void run() { 
     runWithTyConstr(ExprTypeConstructor.get); 
    } 

    <E extends Type.Constructor> void runWithTyConstr(ExprTypeConstructor.Is<E> tyConstrKnowledge) { 
     Expr<Fix<E>> one = Expr.lit(1); 
     Expr<Fix<E>> two = Expr.lit(2); 

     // convertToTypeApp method is generated by annotation processor 
     Type.App<E, Fix<E>> oneAsTyApp = tyConstrKnowledge.convertToTypeApp(one); 
     Type.App<E, Fix<E>> twoAsTyApp = tyConstrKnowledge.convertToTypeApp(two); 

     Fix<E> oneFix = new Fix<>(oneAsTyApp); 
     Fix<E> twoFix = new Fix<>(twoAsTyApp); 

     Expr<Fix<E>> addition = Expr.add(oneFix, twoFix); 
     process(addition, tyConstrKnowledge); 
    } 

    <E extends Type.Constructor> void process(
      Fix<E> fixedPoint, 
      ExprTypeConstructor.Is<E> tyConstrKnowledge) { 

     Type.App<E, Fix<E>> inTyApp = fixedPoint.getIn(); 

     // convertToExpr method is generated by annotation processor 
     Expr<Fix<E>> in = tyConstrKnowledge.convertToExpr(inTyApp); 

     for (Fix<E> subExpr: in.getSubExpressions()) { 
      process(subExpr, tyConstrKnowledge); 
     } 
    } 

}