Несмотря на то, что монады могут быть реализованы в Java, любые вычисления с их участием обречены стать беспорядочной смесью дженериков и фигурных скобок.
Я бы сказал, что Java определенно не является тем языком, который можно использовать для иллюстрации их работы или изучения их значения и сути. Для этой цели гораздо лучше использовать JavaScript или доплатить и изучить Haskell.
В любом случае, я сообщаю вам, что я только что реализовал монаду состояния, используя новые лямбда-выражения Java 8. Это определенно любимый проект, но он работает на нетривиальном тестовом примере.
Вы можете найти его на моем blog, но здесь я дам вам некоторые подробности.
Монада состояния — это, по сути, функция перехода от состояния к паре (состояние, содержимое). Обычно вы присваиваете состоянию общий тип S, а содержимому — универсальный тип A.
Поскольку в Java нет пар, мы должны моделировать их с помощью определенного класса, назовем его Scp (пара состояние-содержимое), который в этом случае будет иметь общий тип Scp<S,A>
и конструктор new Scp<S,A>(S state,A content)
. После этого мы можем сказать, что монадическая функция будет иметь тип
java.util.function.Function<S,Scp<S,A>>
который является @FunctionalInterface
. Это означает, что его единственный метод реализации можно вызвать без его имени, передав лямбда-выражение с правильным типом.
Класс StateMonad<S,A>
в основном является оболочкой функции. Его конструктор может быть вызван, например. с участием
new StateMonad<Integer, String>(n -> new Scp<Integer, String>(n + 1, "value"));
Монада состояния хранит функцию как переменную экземпляра. Затем необходимо предоставить общедоступный метод для доступа к нему и передать ему состояние. Я решил назвать его s2scp
("состояние-состояние-пара").
Чтобы завершить определение монады, вы должны указать unit (он же return) и bind (он же flatMap) метод. Лично я предпочитаю указывать модуль как статический, тогда как bind является членом экземпляра.
В случае монады состояния единица измерения должна быть следующей:
public static <S, A> StateMonad<S, A> unit(A a) {
return new StateMonad<S, A>((S s) -> new Scp<S, A>(s, a));
}
в то время как bind (как член экземпляра):
public <B> StateMonad<S, B> bind(final Function<A, StateMonad<S, B>> famb) {
return new StateMonad<S, B>((S s) -> {
Scp<S, A> currentPair = this.s2scp(s);
return famb(currentPair.content).s2scp(currentPair.state);
});
}
Вы заметили, что bind должен ввести общий тип B, потому что это механизм, который позволяет связывать монады разнородных состояний и дает этой и любой другой монаде замечательную возможность перемещать вычисления от типа к типу.
Я бы остановился здесь с кодом Java. Сложный материал находится в проекте GitHub. По сравнению с предыдущими версиями Java, в лямбда-выражениях удалено много фигурных скобок, но синтаксис по-прежнему довольно запутан.
Кроме того, я показываю, как аналогичный код состояния монады может быть написан на других основных языках. В случае Scala bind (который в этом случае должен называться flatMap) читается как
def flatMap[A, B](famb: A => State[S, B]) = new State[S, B]((s: S) => {
val (ss: S, aa: A) = this.s2scp(s)
famb(aa).s2scp(ss)
})
тогда как привязка в JavaScript — моя любимая; 100% функциональный, скудный и подлый, но, конечно же, бестиповый:
var bind = function(famb){
return state(function(s) {
var a = this(s);
return famb(a.value)(a.state);
});
};
‹shameless› Здесь я срезаю несколько углов, но если вас интересуют подробности, вы найдете их в моем блоге WP.‹/shameless›
person
Marco Faustinelli
schedule
27.04.2014