1

私は最近、Haskell の Monad - State を見てきました。このモナドで動作する関数を作成できましたが、動作をクラスにカプセル化しようとしています。基本的に、Haskell で次のようなものを複製しようとしています。

class A {
  public:
  int x;
  void setX(int newX) {
      x = newX;
  }
  void getX() {
      return x;
  }
}

誰かがこれを手伝ってくれるなら、私はとても感謝しています。ありがとう!

4

3 に答える 3

7

控えめに言っても、Haskell は従来の OO スタイルの開発を奨励していないことに注意することから始めます。代わりに、他の多くの言語では見られないような「純粋な関数」操作に適した機能と特性を備えています。簡単に言えば、他の (伝統的な言語) から概念を「持ち込む」ことは、しばしば非常に悪い考えになる可能性があるということです。

しかし、私は振る舞いをクラスにカプセル化しようとしています

したがって、頭に浮かぶ私の最初の主要な質問は、なぜですか? 確かに、このクラス (従来の OO の概念) で何かをしたいに違いありませんか?

この質問に対するおおよその答えが「ある種のデータ構造をモデル化したい」である場合、次のようなものを使用する方がよいでしょう。

data A = A { xval :: Int }

> let obj1 = A 5
> xval obj1
5
> let obj2 = obj1 { xval = 10 }
> xval obj2
10

これは、「ゲッター」関数と破壊的な更新 (レコード構文を使用) とともに、純粋で不変のデータ構造を示しています。このようにして、必要に応じて、これらの「データ構造」を新しいデータ構造にマッピングする関数の組み合わせとして、必要な作業を行うことができます。

さて、ある種の状態モデルが絶対に必要な場合 (実際、この質問に答えるには、ローカル状態とグローバル状態が何であるかを正確に知るには少し経験が必要です)、その場合にのみ、次のような状態モナドの使用を掘り下げます。

module StateGame where

import Control.Monad.State

-- Example use of State monad
-- Passes a string of dictionary {a,b,c}
-- Game is to produce a number from the string.
-- By default the game is off, a C toggles the
-- game on and off. A 'a' gives +1 and a b gives -1.
-- E.g 
-- 'ab'    = 0
-- 'ca'    = 1
-- 'cabca' = 0
-- State = game is on or off & current score
--       = (Bool, Int)

type GameValue = Int
type GameState = (Bool, Int)

playGame :: String -> State GameState GameValue
playGame []     = do
    (_, score) <- get
    return score

playGame (x:xs) = do
    (on, score) <- get
    case x of
         'a' | on -> put (on, score + 1)
         'b' | on -> put (on, score - 1)
         'c'      -> put (not on, score)
         _        -> put (on, score)
    playGame xs

startState = (False, 0)

main = print $ evalState (playGame "abcaaacbbcabbab") startState

(恥知らずにこのチュートリアルから削除)。状態モナド内に含まれる状態へのアクセスを容易にする「put」および「get」モナド関数に加えて、状態モナドのコンテキストでの類似の「純粋な不変データ構造」の使用に注意してください。

最後に、この (OO) クラスのモデルで本当に達成したいことは何ですか? 自問することをお勧めします。Haskell は典型的な OO 言語ではありません。概念を 1 対 1 でマッピングしようとすると、短期的に (場合によっては長期的に) イライラするだけです。これは標準的なマントラであるはずですが、本Real World Haskellから学ぶことを強くお勧めします。著者は、あるツールまたは抽象化を別のものよりも選択するためのはるかに詳細な「動機」を掘り下げることができます。断固として、従来の OO 構造を Haskell でモデル化することもできますが、そうするのに本当に正当な理由がない限り、これを行うことはお勧めしません。

于 2011-06-01T15:43:56.890 に答える
4

命令型コードを純粋に機能的なコンテキストに変換するには、少し並べ替える必要があります。

セッターはオブジェクトを変更します。怠惰と純粋​​さのため、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のコンビネータを使用します。

上記の他のレンズ ライブラリは、同様のコンビネータを提供します。

于 2011-06-01T18:57:32.277 に答える
0

まず第一に、あなたが本当に理由を知らない限り、これはおそらく間違った方法であるという Raeez に同意します! ある値を 42 (たとえば) 増やしたい場合、それを行う関数を作成してみませんか?

これは、ボックスに値が入っていて、それらを取り出して操作し、元に戻すという従来の OO の考え方とは大きく異なります。引数であり、最後にそれをわずかに変更して返し、他の値と組み合わせて、すべての配管が乱雑になっています!」Stateモナドは本当に必要ありません。(学習する) Haskell の楽しみの 1 つは、ステートフルな OO の考え方を回避する新しい方法を見つけることです!

そうは言っても、絶対にxof 型を含むボックスが必要な場合は、次のようなInt独自のバリアントを作成してみてgetくださいput

import Control.Monad.State

data Point = Point { x :: Int, y :: Int } deriving Show

getX :: State Point Int
getX = get >>= return . x

putX :: Int -> State Point ()
putX newVal = do
    pt <- get
    put (pt { x = newVal })

increaseX :: State Point ()
increaseX = do
    x <- getX
    putX (x + 1)

test :: State Point Int
test = do
    x1 <- getX
    increaseX
    x2 <- getX
    putX 7
    x3 <- getX
    return $ x1 + x2 + x3

ここで、runState test (Point 2 9)ghci で評価すると、元に戻ります(12,Point {x = 7, y = 9})(12 = 2 + (2+1) + 7 であり、xin the state は最後に 7 に設定されるため)。返されたポイントを気にしない場合は、使用できevalState12.

Point私の意見では、動作するものを持つデータ構造が複数ある場合に備えて、型クラスで抽象化するなど、ここで行うべきより高度なこともありますが、それxは別の質問に任せたほうがよいでしょう!

于 2011-06-01T18:15:03.507 に答える