Незважаючи на те, що монади можуть бути реалізовані на Java, будь-які обчислення, що їх залучають, приречені стати безладним поєднанням дженериків та фігурних дужок.
Я б сказав, що Java, безумовно, не є тією мовою, яка використовується для того, щоб проілюструвати їх роботу чи вивчити їх значення та суть. Для цього набагато краще використовувати JavaScript або заплатити якусь додаткову ціну та вивчити Haskell.
У будь-якому випадку, я повідомляю вам, що я щойно реалізував монаду стану, використовуючи нові лямбди Java 8 . Це, безумовно, проект для домашніх тварин, але він працює на нетривіальному тестовому випадку.
Можливо, ви знайдете його представленим у моєму щоденнику , але я розповім вам тут дещо.
Державна монада в основному є функцією від стану до пари (стану, вмісту) . Зазвичай ви надаєте штату загальний тип 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
("пара до стану-вмісту").
Щоб завершити визначення монади, вам потрібно надати одиницю (вона ж повернена ) та метод прив'язки (він же 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));
}
while 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>