Требуется немного перестановок, чтобы преобразовать императивный код в чисто функциональный контекст.
Сеттер изменяет объект.Нам не разрешено делать это непосредственно в Хаскеле из-за лени и чистоты.
Возможно, если мы переведем Государственную монаду на другой язык, это будет более очевидным.Ваш код написан на C ++, но, поскольку я, по крайней мере, хочу сборку мусора, я буду использовать здесь Java.
Поскольку Java никогда не удосужилась определить анонимные функции, сначала мы определим интерфейс для чистых функций.
public interface Function<A,B> {
B apply(A a);
}
Тогда мы можем создать чистый неизменный тип пары.
public final class Pair<A,B> {
private final A a;
private final B b;
public Pair(A a, B b) {
this.a = a;
this.b = b;
}
public A getFst() { return a; }
public B getSnd() { return b; }
public static <A,B> Pair<A,B> make(A a, B b) {
return new Pair<A,B>(a, b);
}
public String toString() {
return "(" + a + ", " + b + ")";
}
}
Имея в руках, мы можем фактически определить монаду State:
public abstract class State<S,A> implements Function<S, Pair<A, S> > {
// pure takes a value a and yields a state action, that takes a state s, leaves it untouched, and returns your a paired with it.
public static <S,A> State<S,A> pure(final A a) {
return new State<S,A>() {
public Pair<A,S> apply(S s) {
return new Pair<A,S>(a, s);
}
};
}
// we can also read the state as a state action.
public static <S> State<S,S> get() {
return new State<S,S>() {
public Pair<S,S> apply(S, s) {
return new Pair<S,S>(s, s);
}
}
}
// we can compose state actions
public <B> State<S,B> bind(final Function<A, State<S,B>> f) {
return new State<S,B>() {
public Pair<B,S> apply(S s0) {
Pair<A,S> p = State.this.apply(s0);
return f.apply(p.getFst()).apply(p.getSnd());
}
};
}
// we can also read the state as a state action.
public static <S> State<S,S> put(final S newS) {
return new State<S,S>() {
public Pair<S,S> apply(S, s) {
return new Pair<S,S>(s, newS);
}
}
}
}
Теперь существует понятие геттера и сеттера внутри государственной монады.Это так называемые линзы.Базовая презентация на Java будет выглядеть так:
public abstract class Lens[A,B] {
public abstract B get(A a);
public abstract A set(B b, A a);
// .. followed by a whole bunch of concrete methods.
}
Идея состоит в том, что линза обеспечивает доступ к получателю, который знает, как извлечь B из A, и к установщику, который знает, как взять Bи некоторый старый A, и заменить часть A, получая новый A. Он не может изменить старый, но может создать новый с одним из замененных полей.
Я выступил с докладом.на них на недавней встрече энтузиастов Скала в Бостоне.Вы можете посмотреть презентацию здесь .
Чтобы вернуться в Haskell, вместо того, чтобы говорить о вещах в императивной обстановке.Мы можем импортировать
import Data.Lens.Lazy
из comonad-transformers или и другие библиотеки линз, упомянутые здесь .Эта ссылка предоставляет законы, которые должны быть соблюдены, чтобы быть действительным объективом.
И тогда вам нужен тип данных:
data A = A { x_ :: Int }
с объективом
x :: Lens A Int
x = lens x_ (\b a -> a { x_ = b })
Теперь вы можете написать код, который выглядит как
postIncrement :: State A Int
postIncrement = do
old_x <- access x
x ~= (old_x + 1)
return old_x
, используя комбинаторы из Data.Lens.Lazy .
Другие библиотеки объективов, упомянутые вышепредоставить аналогичные комбинаторы.