4

LYAH や Haskell Wiki などの非常に明確な説明を読んだにもかかわらず、状態モナドがどのように実装されているかについてまだ混乱しています。自信がありませんが、それが何であるかを理解していると思います。

それでは、簡単なデータ型があるとしましょう:

data Simple a = Top a 
  deriving ( Show )

この:

newtype SimpleState a = SimpleState { applySimple :: Int -> ( a, Int ) }

次に、SimpleState をモナドにします

instance Monad SimpleState where
    return x = SimpleState $ \s -> ( x, s )
    st >>= g = SimpleState $ \s -> let ( x, s' ) = applySimple st s in applySimple ( g x ) s' 

質問 1: ラムダは s (状態) をパラメーターとしてどのように取り込んでいますか? どのように渡されますか?

質問 2:applySimpleが関数シグネチャに 1 つのパラメーターを取り込んでいる場合、なぜapplySimple st sラムダ内にあるのですか? なぜapplySimple2回適用されるのですか?

さらに紛らわしいことに、これは状態を変更します。

tic :: SimpleState Int
tic = SimpleState $ \s -> ( s, s + 1 ) 

質問 3. これは何ですか? SimpleState に対して何らかのアクションを実行しているのに、その署名が関数ではないのはなぜですか?

これで、tic をこの関数に渡すことができます。

incr :: Simple a -> SimpleState ( Simple ( a, Int ) ) 
incr ( Top a ) = do 
  v <- tic
  return ( Top ( a, v ) )

質問 4: tic を と一緒に使用できますか / どのように使用し>>=ますか?

そして、次のように使用します。

applySimple ( incr ( Top 1 ) ) 3

私はこれを得る:

(Top (1,3),4)

繰り返しますが、applySimple私を混乱させる2つのパラメーターに適用されます。

要約すると、コンストラクターが s をパラメーターとして取り込む関数を取り込んでいるという事実に本当に夢中になっており、SimpleStateそれがコンテキストでどのように使用されているのかがわかりません。

4

3 に答える 3

11

さて、あなたはたくさんの質問を 1 つの投稿にまとめました...

1.ラムダパラメータはどこsから来たのですか?

return x = SimpleState $ \s -> ( x, s )

コンストラクターを見ると、型の関数を引数としてSimpleState取ることがわかります。Int -> (a, Int)したがって、正しい型の関数を提供するためにラムダが使用されます。Lambda は、関数を作成する 1 つの方法にすぎません。

関数が必要な理由は、関数パラメーターを介してのみ状態モナドの現在の状態にアクセスできるためです。

2. なぜapplySimple2 つのパラメータを取るのですか?

これは、フィールド アクセサーだからです。

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

の型はx何ですか? そうですねPoint -> Int。値からフィールドを抽出しPointます。

origin = Point 0 0
potOfGold = Point 15 3

main = putStrLn $ "Pot of gold is at (" ++ show (x potOfGold) ++ ", " ++
                  show (y potOfGold) ++ ")"

明らかに、 にはタイプがxありません。同じく、IntPoint

newtype MyState a = MyState { runState :: Int -> (a, Int) }

の型はrunState何ですか? そうですねMyState -> Int -> (a, Int)。値からフィールド (唯一のフィールド) を抽出しMyState aます。

tic関数ではないのはなぜですか?

モナド アクションは関数である必要はありません。たとえば、次のコードについて考えてみますputStrLn

main = putStrLn "Hello, world."

putStrLn関数だから何かをするのは理にかなっていますよね?まあ、あなたはそれが何かをするのは正しいですが、あなたの推論は間違っています. 関数型言語に命令的直観を使用しています。厳密に言えば、putStrLn何も出力しません。これはモナド アクションを返す関数であり、結果のモナド アクションは を出力し"Hello, world."ます。

printHello :: IO ()
printHello = putStrLn "Hello, world."

明らかに、これは何かをします。パラメータを取らないため、関数ではありません。Haskell を理解したい場合は、次のことを理解してください。「何かを行う」はモナド アクションのプロパティであり、関数とは関係ありません

4. とtic一緒に使えますか / どのように使え>>=ますか?

「機能 X を機能 Y と一緒に使用するにはどうすればよいですか」は、スタック オーバーフローに関する質問に関する限り、私の不満リストの一番上にあります。「キッチンで水をどうやって使うの?」と聞いて、「床をモップで拭いてもいいよ」と言ったのに、喉が渇いて水を飲みたがっているようなものです。「水を使って床を掃除するにはどうすればいいですか?」などの質問をする方がよいでしょう。または「喉の渇きを癒すために水を使用するにはどうすればよいですか?」これらの質問は答えることができます。

ticしたがって、質問 4 の答えは「はい、使用できます>>=」です。そして、「何をしたいのかによって使い方が変わる」。

脚注:人々があなたのコードをf (x y)読みf ( x y )やすくするため、.

于 2013-03-02T04:55:27.440 に答える
10

質問 1: ラムダは s (状態) をパラメーターとしてどのように取り込んでいますか? どのように渡されますか?

次のように定義されたgetとの古典的な定義を使用しましょう。put

put :: Int -> SimpleState ()
put n = SimpleState (\_ -> ((), n))

get :: SimpleState Int
get = SimpleState (\s -> (s, s))

を呼び出すと、型の関数を公開するapplySimpleをアンラップします。次に、その関数を初期状態に適用します。いくつかの具体的な例を使って試してみましょう。SimpleStateInt -> (a, Int)

まず、コマンドを実行します。put 1初期状態は次の0とおりです。

applySimple (put 1) 0

-- Substitute in definition of 'put'
= applySimple (SimpleState (\_ -> ((), 1))) 0

-- applySimple (Simple f) = f
(\_ -> ((), 1)) 0

-- Apply the function
= ((), 1)

putが初期状態を無視し、右側の状態スロットを単に に置き換え、左側の戻り値スロットを1残していることに注目してください。()

次に、開始状態を使用して get コマンドを実行してみましょう0:

applySimple get 0

-- Substitute in definition of 'get'
= applySimple (SimpleState (\s -> (s, s))) 0

-- applySimple (SimpleState f) = f
= (\s -> (s, s)) 0

-- Apply the function
= (0, 0)

get左の戻り値スロットにコピー0するだけで、右の状態スロットは変更されません。

SimpleStateしたがって、初期状態をそのラムダ関数に渡す方法は、 newtype をアンラップして基礎となるラムダ関数を公開し、ラムダ関数を初期状態に直接適用するだけです。

質問 2: applySimple が関数シグネチャで 1 つのパラメーターを使用している場合、なぜラムダ内に applySimple st があるのですか? applySimpleapplied が 2 回適用されるのはなぜですか?

これは、applySimpleのタイプが ではないためInt -> (a, Int)です。それは実際には:

applySimple :: SimpleState -> Int -> (a, Int)

これは、Haskell のレコード構文の紛らわしい側面です。次のようなレコードフィールドがあるときはいつでも:

data SomeType { field :: FieldType }

... thenfieldのタイプは実際には次のとおりです。

field :: SomeType -> FieldType

私はそれが奇妙であることを知っています。

質問 3. これは何ですか? SimpleState に対して何らかのアクションを実行しているのに、その署名が関数ではないのはなぜですか?

SimpleStatenewtype は、ラップする関数を隠します 。newtypesアンラップするまで、何でも隠すことができます。あなたSimpleStateはその中に関数を持っていますが、あなたがそれをアンラップするまで、コンパイラが見るのは外側の newtype だけですapplySimple

質問 4: >>= で tic を使用できますか / どのように使用しますか?

の定義を間違えていますincr。正しい使い方ticは次のようになります。

ticTwice :: SimpleState ()
ticTwice = do
    tic
    tic

...コンパイラはこれを次のように変換します:

ticTwice =
    tic >>= \_ ->
    tic

および ticの定義を使用して(>>=)、これにより値が 2 増加することを証明できます。

tic >>= \_ -> tic

-- Substitute in definition of `(>>=)`
SimpleState (\s ->
    let (x, s') = applySimple tic s
    in  applySimple ((\_ -> tic) x) s')

-- Apply the (\_ -> tic) function
SimpleState (\s ->
    let (x, s') = applySimple tic s
    in  applySimple tic s')

-- Substitute in definition of `tic`
SimpleState (\s ->
    let (x, s') = applySimple (SimpleState (\s -> (s, s + 1))) s
    in  applySimple (SimpleState (\s -> (s, s + 1))) s'

-- applySimple (SimpleState f) = f
SimpleState (\s ->
    let (x, s') = (\s -> (s, s + 1)) s
    in  (\s -> (s, s + 1)) s'

-- Apply both functions
SimpleState (\s ->
    let (x, s') = (s, s + 1)
    in  (s', s' + 1)

-- Simplify by getting rid of unused 'x'
SimpleState (\s ->
    let s' = s + 1
    in  (s', s' + 1)

-- Simplify some more:
SimpleState (\s -> s + 1, s + 2)

したがって、 をtic使用して 2 つの を連鎖させると、状態を だけインクリメントし、状態プラスを返す(>>=)単一のステートフル関数にそれらを結合することがわかります。21

于 2013-03-02T06:18:01.893 に答える
4

Stateモナドでは、bind はステートフルな関数を構成します (通常の構成と同様です(.)。計算 (つまり、doブロック内のすべてのもの)の構成が完了したら、初期状態値で実行できます。

質問 1: ラムダは s (状態) をパラメーターとしてどのように取り込んでいますか? どのように渡されますか?

直後のラムダバインディングSimpleStateは、最終的にモナド計算を実行する初期状態値です。に制限されたものではなく、状態がタイプを変更できる構造を使用している場合、これはより明確になりますs

applySimple が 2 回適用されるのはなぜですか?

具体的には、ラッパーからapplySimpleあなたを抽出しているだけです。次に、その関数がそれに続くものに適用されます ( )。これは、次のことと同じ考え方です。Int -> ( a, Int )newtypes($) (+1) 1

SimpleState に対して何らかのアクションを実行しているのに、その署名が関数ではないのはなぜですか?

\s->バインドは最初のTBD 状態であることを理解し、構成としてのバインドの観点から考えると、そこにたどり着くと思います。これは のようなモナドとは非常に異なるビーストMaybeです。

FWIW 多くの人が、私が書いたこの古いチュートリアルが役に立ったと感じているようです。

于 2013-03-02T04:55:12.493 に答える