命令型コードを純粋に機能的なコンテキストに変換するには、少し並べ替える必要があります。
セッターはオブジェクトを変更します。怠惰と純粋さのため、Haskell で直接これを行うことは許可されていません。
おそらく、State モナドを別の言語に書き写すと、より明確になるでしょう。コードは 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.
}
レンズは、A から B を抽出する方法を知っているゲッターと、B と古い A を取り、A の一部を置き換えて新しい A を生成する方法を知っているセッターへのアクセスを提供するというものです。古いものを変更することはできませんが、フィールドの 1 つを置き換えて新しいものを構築することはできます。
最近のボストン地域の Scala Enthusiasts ミーティングで、これらについて講演しました。プレゼンテーションはこちらからご覧いただけます。
命令的な状況で物事について話すのではなく、Haskell に戻ること。輸入できます
import Data.Lens.Lazy
comonad-transformersまたはここで言及されている他のレンズ ライブラリの 1 つから。そのリンクは、有効なレンズであるために満たさなければならない法律を提供します。
そして、あなたが探しているのは、次のようなデータ型です:
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のコンビネータを使用します。
上記の他のレンズ ライブラリは、同様のコンビネータを提供します。