Высшие сородичи в Java

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

public class FixExpr {
  Expr<FixExpr> in;
}

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

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

Но Eclipse не нравится:

Тип F не является общим; он не может быть параметризован с помощью аргументов < Fix <F →

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

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

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

Ответ 1

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

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

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

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

public BufferedImage<String> bar()

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

Ответ 2

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


[EDIT by Rahul G]

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

trait Expr[+A]

trait FixExpr {
  val in: Expr[FixExpr]
}

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

Ответ 3

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

UPDATE: строка

F<Fix<F>> in;

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

Fix<F> in;

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

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

Ответ 4

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

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

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

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

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

Эта аннотация генерирует класс ExprTypeConstructor. Теперь вы можете обработать Fix 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);
        }
    }

}

Ответ 5

Похоже, вам может понадобиться что-то вроде:

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

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