Дженерики высшего порядка в 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) }

person Martijn    schedule 18.05.2009    source источник
comment
Какую реальную проблему вы пытаетесь решить этим? Разве не может быть проще решить эту проблему с помощью шаблона?   -  person KitsuneYMG    schedule 18.05.2009


Ответы (6)


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

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

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

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

public BufferedImage<String> bar()

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

person Zarkonnen    schedule 18.05.2009
comment
Он не поддерживается напрямую Java, но вы все равно можете закодировать эту концепцию в системе типов Java. Смотрите мой ответ ниже. - person michid; 03.01.2021

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


[ИЗМЕНИТЬ, Рахул Дж.]

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

trait Expr[+A]

trait FixExpr {
  val in: Expr[FixExpr]
}

trait Fix[F[_]] {
  val in: F[Fix[F]]
}
person ZelluX    schedule 25.05.2009

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

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

F<Fix<F>> in;

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

Fix<F> in;

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

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

person Hank Gay    schedule 18.05.2009
comment
Не могли бы вы немного уточнить? Я объявил, что тип принимает параметр типа - Fix ‹F›. Или ты не об этом? - person Martijn; 18.05.2009
comment
Но выбранный вами тип - ‹Fix ‹F›› - не универсален; это работает только для Fix ‹f› - вы должны объявить его как type ‹Fix›, не так ли? - person sanbikinoraion; 18.05.2009
comment
Пожалуйста. Я плохо разбираюсь в теории типов, так что пока это было сложно, но довольно интересно. Я рад, что нашел ваш вопрос сегодня утром, иначе я, возможно, никогда не читал газету. - person Hank Gay; 18.05.2009
comment
В ответ на предложение вы посмотрите на Scala. Вы можете довольно свободно смешивать Scala и Java, так что это достаточно легко для вас. - person Zarkonnen; 18.05.2009
comment
повторно смешивать свободно: вероятно, не удастся использовать более высокодородный тип Scala из Java. - person Erik Kaplun; 20.02.2014

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

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

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

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

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

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

}
person Victor Nazarov    schedule 15.08.2016

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

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

(См. Класс Enum и вопросы о его обобщениях.)

person Tom Hawtin - tackline    schedule 18.05.2009
comment
Привет, Том, это решение выглядит действительно интересным, но я еще не совсем понимаю, что оно делает. Разве это не налагает на F некую иерархию? Я не хочу этого делать - все, что я прошу от F, - это чтобы он по-прежнему получал аргумент типа для завершения. - person Martijn; 18.05.2009
comment
Если это так, то в java невозможно делать то, что вы хотите - язык недостаточно выразителен. - person Chii; 18.05.2009
comment
Он говорит, что F - это разновидность Fix. Поскольку Fix является универсальным, ему необходимо указать правильный универсальный аргумент. Ваш вопрос крайне абстрактный. Я понимаю, что у Haskell сложная система типов, отличная от Java. Неудивительно, что нет карты 1: 1 для каждой функции. - person Tom Hawtin - tackline; 18.05.2009

Как указал Виктор, существует обходной способ кодирования высокодородных типов в Java. Суть его в том, чтобы ввести тип H<F, T> для кодирования F<T>. Затем это можно использовать для кодирования фиксированной точки функторов (т.е. типа Fix Haskell):

public interface Functor<F, T> {
    <R> H<F, R> map(Function<T, R> f);
}

public static record Fix<F extends H<F, T> & Functor<F, T>, T>(F f) {
    public Functor<F, Fix<F, T>> unfix() {
        return (Functor<F, Fix<F, T>>) f;
    }
}

Отсюда вы можете продолжить и реализовать катаморфизмы над исходными алгебрами:

public interface Algebra<F, T> extends Function<H<F, T>, T> {}

public static <F extends H<F, T> & Functor<F, T>, T> Function<Fix<F, T>, T> cata(Algebra<F, T> alg) {
    return fix -> alg.apply(fix.unfix().map(cata(alg)));
}

См. Мое репозиторий GitHub для работы код, включая несколько примеров алгебр. (Обратите внимание, IDE, как IntelliJ, борются с кодом, хотя он компилируется и отлично работает с Java 15).

person michid    schedule 02.01.2021