13

この優れたチュートリアルから State モナドについて学んでいます。しかし、プログラマーではない人に説明しようとすると、困惑する質問がありました。

状態の目的が可変メモリをシミュレートすることである場合、状態モナドが格納する関数が次の型であるのはなぜですか。

s -> (a, s)

単純ではありません:

s -> s

言い換えれば、「中間」値の必要性は何ですか? たとえば、必要な場合は、状態を のタプルとして定義するだけでシミュレートできません(state, value)か?

私は何かを混乱させたと確信しています。

4

7 に答える 7

17

C のような命令型言語との類似s -> s点は、戻り値の型が である関数に相当します。この関数は、void純粋に副作用 (メモリの変更など) のために呼び出されます。に同型State s ()です。

実際、グローバル変数のみを介して通信する C 関数を作成することは可能です。ただし、C の場合と同様に、関数から値を返すと便利なことがよくあります。それaがそのためです。

もちろん、特定の問題についてs -> sは、より良い選択である可能性があります。モナドではありませんが、モノイドです(Endoでラップされた場合)。そのため、Monad の and に対応する<>andを使用して、そのような関数を構築できます。mempty>>=return

于 2012-07-20T16:57:36.623 に答える
5

ニックの答えを少し拡張するには:

s状態です。すべての関数がs -> s(状態から状態へ) だった場合、関数は値を返すことができません。状態を として定義することもできます(the actual state, value returned)が、それは状態をステートフルな関数が計算している値と混同してしまいます。また、関数で実際に計算して値を返すこともよくあります...

于 2012-07-20T16:44:53.580 に答える
2

s' -> s'と同等(a, s) -> (a, s)です。ここでは、 に加えて、物事を開始するStateためにイニシャルが必要であることは明らかです。as

一方、物事を開始s -> (a, s)するためのシードのみが必要であり、値sはまったく必要ありません。a

したがって、 の型は、が の場合よりも複雑ではないことを示していs -> (a, s)ます。Haskell の型は、多くの情報を伝えます。State(a, s) -> (a, s)

于 2012-07-20T17:51:06.327 に答える
2

の目的がState可変メモリをシミュレートすることである場合、状態モナドが格納する関数が次の型であるのはなぜですか。

s -> (a, s)

単純ではありません:

s -> s

モナドの目的は、State可変メモリをシミュレートすることではなく、値を生成し副作用を持つ計算をモデル化することです。簡単に言えば、 type の初期状態が与えられると、計算によって type の値と更新された状態が生成されます。sa

たぶん、あなたの計算は値を生成しません...それなら、簡単です: 値の型aは単純()です。おそらく一方で、あなたの計算には副作用がありません。s -> s繰り返しますが、簡単です。状態遷移関数 ( への引数modify) は単に であると考えるかもしれませんid。しかし、多くの場合、両方を同時に扱っています。


実際にはgetandputを比較的単純な例として使用できます。

get :: State s s      -- s -> (s, s)
put :: s -> State ()  -- s -> (s -> ((), s))
  • get現在の状態 (最初のs) を指定すると、それを値 (つまり、計算の結果) と「新しい」(変更されていない) 状態の両方として返す計算です。

  • putは、新しい状態 (最初のs) と現在の状態 (2 番目のs) が与えられると、現在の状態を単純に無視する計算です。計算された値として生成()され (もちろん、値は計算されていないためです!)、提供された新しい状態にハングアップします。

于 2012-07-23T17:17:03.607 に答える
1

doおそらく、記法内でステートフルな計算を使用したいですか?

Monadによって定義されたステートフルな計算のインスタンスがどのように見えるかを自問する必要があります。

newtype State s = { runState :: s -> s }
于 2012-07-20T17:15:35.943 に答える
0

aは返される値で、sは最終状態です。

http://www.haskell.org/haskellwiki/State_Monad#Implementation

于 2012-07-20T16:39:30.683 に答える
0

解決すべき問題は、入力と一連の関数があり、関数を順番に入力に適用することです。

関数が純粋に状態を変更する関数でs -> sあり、 type の入力に対して、それらを使用する必要sはありません。Haskell は、これらのような関数を連鎖させるのが非常に得意です。 State.foldr (.) idfoldr id

ただし、関数が両方とも状態変更し、その結果を報告する場合は、それらに type を与えることができるため、s -> (s,a)それらをすべてくっつけるのは少し面倒です。結果のタプルをアンパックし、新しい状態値を次の関数に渡し、報告された値を別の場所で使用してから、その結果をアンパックする必要があります。アンパックを行うには、各結果に名前を付けて明示的に入力する必要があるため、間違った状態を入力関数に渡すのは簡単です。次のような結果になります。

let
  (res1, s1) = fun1 s0
  (res2, s2) = fun2 s1
  (res3, s3) = fun3 res1 res2 s1
  ...
  in resN

後で 2 行目を追加し、3 行目を変更する必要があることに気づかなかったために、誤っs1て の代わりにパスしてしまいました。s2関数を構成するとき、s -> s正しい名前がないため、この問題はおそらく発生しません。

let
  resN = fun1 . fun2 . fun3 . -- etc.

だから私たちStateは同じトリックをするために発明しました。基本的には、適切な状態が常に適切な関数に渡されるように、State関数のようなものを接着する方法にすぎません。s -> (s,a)

だから、「 を使いたい、使おう」というよりは、「 のような関数を書いている、それを簡単にするために発明しよう」という人が多かっStates -> (s,a)のですs -> (s,a)Statefunctionss -> sを使用すると、すでに簡単であり、何も発明する必要はありません。

が自然に発生する例として、s -> (s,a)構文解析を考えてみましょう。パーサーは何らかの入力を受け取り、入力の一部を消費して値を返します。Haskell では、これは自然に入力リストを取得し、値と残りの入力のペア、つまり[Input] -> ([Input], a)orを返すものとしてモデル化されState [Input]ます。

于 2012-07-31T15:55:01.707 に答える