Родовые дженерики в Java - PullRequest
31 голосов
/ 18 мая 2009

Предположим, у меня есть следующий класс:

public class FixExpr {
  Expr<FixExpr> in;
}

Теперь я хочу представить общий аргумент, абстрагируясь от использования Expr:

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

Но Затмению это не нравится:

Тип F не является универсальным; его нельзя параметризовать аргументами >

Возможно ли это вообще или я что-то упустил из-за того, что этот конкретный экземпляр сломался?

Некоторая справочная информация: в Haskell это распространенный способ написания универсальных функций; Я пытаюсь перенести это на Java. Аргумент типа F в приведенном выше примере имеет тип * -> * вместо обычного вида *. В Хаскеле это выглядит так:

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

Ответы [ 5 ]

25 голосов
/ 25 мая 2009

Может быть, вы можете попробовать Scala, функциональный язык, работающий на JVM, который поддерживает генерики более высокого класса.


[РЕДАКТИРОВАТЬ Rahul G ]

Вот как ваш конкретный пример примерно переводится на Scala:

trait Expr[+A]

trait FixExpr {
  val in: Expr[FixExpr]
}

trait Fix[F[_]] {
  val in: F[Fix[F]]
}
24 голосов
/ 18 мая 2009

Я думаю, что то, что вы пытаетесь сделать, просто не поддерживается обобщениями Java. Более простой случай

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

также не компилируется с использованием javac.

Поскольку Java во время компиляции не знает, что такое T, она не может гарантировать, что T<String> вообще имеет смысл. Например, если вы создали Foo<BufferedImage>, bar будет иметь подпись

public BufferedImage<String> bar()

что бессмысленно. Поскольку не существует механизма, который заставлял бы вас создавать экземпляры Foo s с помощью обобщенных T s, он отказывается компилироваться.

5 голосов
/ 18 мая 2009

Чтобы передать параметр типа, определение типа должно объявить, что оно его принимает (оно должно быть generic ). Очевидно, ваш F не является универсальным типом.

ОБНОВЛЕНИЕ: строка

F<Fix<F>> in;

объявляет переменную типа F, которая принимает параметр типа, значение которого равно Fix, которая сама принимает параметр типа, значение которого равно F. F даже не определен в вашем примере. Я думаю, что вы можете хотите

Fix<F> in;

Это даст вам переменную типа Fix (тип, который вы определили в вашем примере), которому вы передаете параметр типа со значением F. Поскольку Fix определен для принятия параметра типа, это работает.

ОБНОВЛЕНИЕ 2: Перечитайте свой заголовок, и теперь я думаю, что вы, возможно, пытаетесь сделать что-то похожее на подход, представленный в «На пути к равным правам для типов с более высоким родом» (предупреждение в формате PDF). Если это так, Java не поддерживает это, но вы можете попробовать Scala.

1 голос
/ 15 августа 2016

Тем не менее, есть способы кодировать более родовые дженерики в Java. Пожалуйста, взгляните на проект с более высоким уровнем развития .

Используя это как библиотеку, вы можете изменить свой код следующим образом:

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

Вам, вероятно, следует добавить аннотацию @GenerateTypeConstructor к вашему Expr классу

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

Эта аннотация генерирует класс ExprTypeConstructor. Теперь вы можете обработать свой Fix of Expr следующим образом:

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);
        }
    }

}
0 голосов
/ 18 мая 2009

Выглядит так, как будто вы хотите что-то вроде:

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

(См. Класс Enum и вопросы по его дженерикам.)

...