Прежде чем вы начнете читать: этот вопрос касается не понимания монад, а определения ограничений системы типа Java, которые препятствуют объявлению интерфейса Monad
.
В попытке понять монады я прочитал этот SO-ответ Эрика Липперта по вопросу, который спрашивает об простом объяснении монадов. Там он также перечисляет операции, которые могут выполняться на монаде:
- Что есть способ взять значение unamplified type и превратить его в значение усиленного типа.
- Что есть способ преобразовать операции над неуправляемым типом в операции над усиленным типом, который подчиняется правилам функционального состава, упомянутым ранее
- Как правило, вы можете получить исключенный тип из расширенного типа. (Эта последняя точка не является строго необходимой для монады, но часто бывает, что такая операция существует.)
После того, как вы прочли больше о монадах, я определил первую операцию как функцию return
, а вторую операцию - как функцию bind
. Я не смог найти обычно используемое имя для третьей операции, поэтому я просто назову его функцией unbox
.
Чтобы лучше понять монады, я пошел вперед и попытался объявить общий Monad
интерфейс в Java. Для этого я сначала рассмотрел подписи трех вышеперечисленных функций. Для Monad M
это выглядит так:
return :: T1 -> M<T1>
bind :: M<T1> -> (T1 -> M<T2>) -> M<T2>
unbox :: M<T1> -> T1
Функция return
не выполняется в экземпляре M
, поэтому она не входит в интерфейс Monad
. Вместо этого он будет реализован как конструктор или метод factory.
Также на данный момент я опускаю функцию unbox
из декларации интерфейса, так как она не требуется. Будут различные реализации этой функции для различных реализаций интерфейса.
Таким образом, интерфейс Monad
содержит только функцию bind
.
Попробуйте объявить интерфейс:
public interface Monad {
Monad bind();
}
Есть два недостатка:
- Функция
bind
должна возвращать конкретную реализацию, однако она возвращает только тип интерфейса. Это проблема, так как мы имеем операции unbox, объявленные на конкретных подтипах. Я буду называть это проблемой 1. - Функция
bind
должна извлекать функцию в качестве параметра. Мы рассмотрим это позже.
Использование конкретного типа в объявлении интерфейса
Это устраняет проблему 1: Если мое понимание монад верно, то функция bind
всегда возвращает новую монаду того же конкретного типа, что и монада, где она была вызвана. Итак, если у меня есть реализация интерфейса Monad
под названием M
, то M.bind
вернет другой M
, но не Monad
. Я могу реализовать это с помощью дженериков:
public interface Monad<M extends Monad<M>> {
M bind();
}
public class MonadImpl<M extends MonadImpl<M>> implements Monad<M> {
@Override
public M bind() { /* do stuff and return an instance of M */ }
}
Поначалу это работает, однако есть два недостатка:
-
Это прерывается, как только класс реализации не обеспечивает себя, а другой вариант реализации интерфейса
Monad
в качестве параметра типаM
, потому что тогда методbind
вернет неправильный тип. Например,public class FaultyMonad<M extends MonadImpl<M>> implements Monad<M> { ... }
вернет экземпляр
MonadImpl
, где он должен вернуть экземплярFaultyMonad
. Однако мы можем указать это ограничение в документации и рассмотреть такую реализацию как ошибку программиста. -
Второй недостаток сложнее разрешить. Я назову его проблемой 2. Когда я пытаюсь создать экземпляр класса
MonadImpl
, мне нужно указать типM
. Попробуем это:new MonadImpl<MonadImpl<MonadImpl<MonadImpl<MonadImpl< ... >>>>>()
Чтобы получить допустимое объявление типа, это должно продолжаться бесконечно. Вот еще одна попытка:
public static <M extends MonadImpl<M>> MonadImpl<M> create() { return new MonadImpl<M>(); }
Пока это работает, мы просто отложили проблему до вызываемого. Вот единственное использование этой функции, которая работает для меня:
public void createAndUseMonad() { MonadImpl<?> monad = create(); // use monad }
который, по существу, сводится к
MonadImpl<?> monad = new MonadImpl<>();
но это явно не то, что мы хотим.
Использование типа в его собственном объявлении со сдвинутыми параметрами типа
Теперь добавьте параметр функции в функцию bind
: Как описано выше, подпись функции bind
выглядит следующим образом: T1 -> M<T2>
. В Java это тип Function<T1, M<T2>>
. Вот первая попытка объявить интерфейс с параметром:
public interface Monad<T1, M extends Monad<?, ?>> {
M bind(Function<T1, M> function);
}
Мы должны добавить тип T1
как общий тип параметра в объявление интерфейса, поэтому мы можем использовать его в сигнатуре функции. Первым ?
является T1
возвращенной монады типа M
. Чтобы заменить его на T2
, мы должны добавить T2
себя в качестве типичного параметра типа:
public interface Monad<T1, M extends Monad<T2, ?, ?>,
T2> {
M bind(Function<T1, M> function);
}
Теперь у нас возникает другая проблема. Мы добавили параметр третьего типа в интерфейс Monad
, поэтому нам пришлось добавить новый ?
к его использованию. Мы будем игнорировать новый ?
, чтобы теперь исследовать теперь первый ?
. Это M
возвращенной монады типа M
. Попробуйте удалить этот ?
, переименовав M
в M1
и введя еще один M2
:
public interface Monad<T1, M1 extends Monad<T2, M2, ?, ?>,
T2, M2 extends Monad< ?, ?, ?, ?>> {
M1 bind(Function<T1, M1> function);
}
Знакомство с другим T3
приводит к:
public interface Monad<T1, M1 extends Monad<T2, M2, T3, ?, ?>,
T2, M2 extends Monad<T3, ?, ?, ?, ?>,
T3> {
M1 bind(Function<T1, M1> function);
}
и введение другого M3
приводит к:
public interface Monad<T1, M1 extends Monad<T2, M2, T3, M3, ?, ?>,
T2, M2 extends Monad<T3, M3, ?, ?, ?, ?>,
T3, M3 extends Monad< ?, ?, ?, ?, ?, ?>> {
M1 bind(Function<T1, M1> function);
}
Мы видим, что это будет продолжаться вечно, если мы попытаемся разрешить все ?
. Это проблема 3.
Подведение итогов
Мы определили три проблемы:
- Использование конкретного типа в объявлении абстрактного типа.
- Создает экземпляр типа, который получает себя как общий тип параметра.
- Объявление типа, который использует себя в своем объявлении со сдвинутыми параметрами типа.
Вопрос: Какая функция отсутствует в системе типа Java? Поскольку существуют языки, которые работают с монадами, эти языки должны как-то объявить тип Monad
. Как эти другие языки объявляют тип Monad
? Я не смог найти информацию об этом. Я только нахожу информацию об объявлении конкретных монадов, таких как монада Maybe
.
Я что-то пропустил? Могу ли я правильно решить одну из этих проблем с помощью системы типа Java? Если я не могу решить проблему 2 с помощью системы типа Java, есть ли причина, по которой Java не предупреждает меня о неинтересном объявлении типа?
Как уже было сказано, этот вопрос не о понимании монадов. Если мое понимание монад ошибочно, вы можете дать нам подсказку, но не пытайтесь дать объяснение. Если мое понимание монад ошибочно, описанные проблемы остаются.
Этот вопрос также не связан с тем, можно ли объявить интерфейс Monad
в Java. Этот вопрос уже получил ответ Эрика Липперта в его SO-ответ, приведенный выше: "Нет". Этот вопрос касается того, что именно является ограничением, которое мешает мне это делать. Эрик Липперт относится к этому как к более высоким типам, но я не могу обойти их вокруг.
Большинство языков ООП не имеют достаточно богатой системы типов, чтобы непосредственно представлять шаблон монады; вам нужна система типов, которая поддерживает типы более высокого типа, чем общие типы. Поэтому я бы не стал этого делать. Скорее, я бы реализовал общие типы, представляющие каждую монаду, и реализовал методы, которые представляют собой три операции, которые вам нужны: превращение значения в усиленное значение, превращение усиленного значения в значение и преобразование функции по незашифрованным значениям в функцию на усиленные значения.