2

私は現在少しの脳のトレーニングを必要としています、そして私はハスケルとモナドでこの記事を見つけました

エクササイズ7reで問題が発生しました。ランダム化された関数バインド。

問題をさらに簡単に実験できるようにするために、StdGenタイプを不特定のタイプに置き換えました。だから代わりに...

bind :: (a -> StdGen -> (b,StdGen)) -> (StdGen -> (a,StdGen)) -> (StdGen -> (b,StdGen))

私は使用しました...

bind :: (a -> c -> (b,c)) -> (c -> (a,c)) -> (c -> (b,c))

そして実際の機能の強化のために(演習から直接)

bind f x seed = let (x',seed') = x seed 
                 in f x' seed'

また、2つのランダム化された関数を試してみてください。

rndf1 :: (Num a, Num b) => a -> b -> (a,b)
rndf1 a s = (a+1,s+1)

rndf2 :: (Num a, Num b) => a -> b -> (a,b)
rndf2 a s = (a+8,s+2)

だから、Haskellコンパイラ(ghci)でこれを使うと、私は...

:t bind rndf2
bind rndf2 :: (Num a, Num c) => (c -> (a, c)) -> c -> (a, c)

rndf2これは、最初のパラメーターとしてカレーされたバインドと一致します。

しかし、私が理解していないのは、どのように...

:t bind rndf2 . rndf1

突然与える

bind rndf2 . rndf1 :: (Num a, Num c) => a -> c -> (a, c)

これは、私たちが作成しようとしている正しいタイプのコンポジションです。

bind rndf2 . rndf1

次のような関数です:

  1. と同じパラメータタイプを取りますrndf1
  2. ANDはからのリターンを受け取り、rndf1それをの入力としてパイプしてrndf2、と同じタイプを返します。rndf2

rndf12つのパラメーターを取りa -> c、これらの関数の構成が次のタイプを持つ必要があることと一致するようにrndf2返すことができます。(a, c)

bind rndf2 . rndf1 :: (Num a, Num c) => a -> c -> (a, c)

これは、私が最初にバインド用に思いついたナイーブなタイプとは一致しません

bind f :: (a -> b -> (c, d)) -> (c, d) -> (e, f)

ここでbindは、神話上、2つのパラメーターを受け取る関数を取り、からの出力をrndf1にフィードできるようにするためにタプルを受け取る関数を生成します。rndf2

  1. バインド関数をそのままコーディングする必要がある理由
  2. バインド関数にナイーブタイプがない理由
4

1 に答える 1

1

bind2つの機能を取る必要があります。「初期」型シグネチャは、1つの関数と1つのタプルを取り、タプルを生成します。これは、この問題には実際には当てはまりませんrndf1。タプルではなく、関数です。パラメータが適用されるrndf1 a sと、タプルになります。したがってbind、2つの機能をとる関数である必要があります。

タプルを2つのパラメーターを受け取る関数にパイプする不思議な力は、の定義にありbindます。

bind :: (a -> c -> (b,c)) -> (c -> (a,c)) -> (c -> (b,c))
bind f x seed = let (x',seed') = x seed 
                 in f x' seed'

この定義では、とは両方ともf関数です(名前をに変更するxと、これはより明確になる可能性があります)。xg

取るlet (x',seed') = x seedx seedタプルを生成します。等号の左側では、そのタプルの個々の要素に新しい名前が付けられ、次の行でそれらの名前が関数に渡されますf

したがって、式を分解すると役立つ場合がありますbind rndf2 . rndf1

すべての関数には、実際には1つのパラメーターがあることに注意してください。のタイプは、rndf1として最も正確に記述できます(Num a , Num b) => a -> (b -> (a,b))。これは、次のことを理解するのに非常に役立ちます。

役立つもう1つのことは、この式をパラメーターでどのように使用するかを考えることです。例:(bind rndf2 . rndf1) a s

すべての関数には1つのパラメーターがあるため、括弧内に1つずつ送信されます。最初に:((bind rndf2 . rndf1) a) s

.これで、演算子なしで式を書き直すことができます(bind rndf2 (rndf1 a)) saが渡されるのrndf1は、それがどのように機能するか.です。ドットの右側の関数が入力を受け取り、その出力を左側の関数に渡します。

の型アノテーションがrndf1 aの2番目のパラメータと一致することがわかりますbind。したがって、パラメータrndf2(rndf1 a)を適用しbindます。

bind rndf2 (rndf1 a) seed = let (x', seed') = (rndf1 a) seed
                             in rndf2 x' seed'

これで、括弧内に1つのseedパラメーターのみを受け取る関数ができました。したがって、を取得sして関数に適用できます。

bind rndf2 (rndf1 a) s = let (x', seed') = (rndf1 a) s
                          in rndf2 x' seed'
于 2012-06-27T21:08:24.540 に答える