Въпреки че монадите могат да бъдат внедрени в Java, всяко изчисление, което ги включва, е обречено да се превърне в объркана комбинация от генерични и фигурни скоби.
Бих казал, че Java определено не е езикът, който да се използва, за да се илюстрира тяхната работа или да се проучи тяхното значение и същност. За тази цел е много по-добре да използвате JavaScript или да платите допълнителна цена и да научите Haskell.
Както и да е, сигнализирам ви, че току-що имплементирах държавна монада с помощта на новите Java 8 lambdas. Това определено е домашен любимец, но работи върху нетривиален тестов случай.
Може да го намерите представен в моя блог, но ще ви дам някои подробности тук.
Монадата на състоянието е основно функция от състояние към двойка (състояние, съдържание). Обикновено давате на състоянието генеричен тип 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 свързването (което в този случай трябва да се нарича 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