2012-06-26 64 views
1

我目前需要有點大腦訓練的,我發現這篇文章Haskell and Monads哈斯克爾單子結合討好

我在與運動7再麻煩。隨機函數綁定。

爲了讓問題變得更簡單,我用一個未指定的類型替換了StdGen類型。因此,而不是...

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

我用...

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

和實際功能impelemtation(剛剛從演習直)

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. 需要與相同的參數類型
  2. 並採取從rndf1和管道它作爲rndf2輸入返回到返回相同類型rndf2

rndf1可以採取2個參數a -> crndf2返回(a, c)以便它匹配,這些功能的組合物應該有類型:

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

這不符合我最初想出了爲綁定

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

這裏bind mythically需要一個函數,它有兩個參數,併產生一個函數,一個元組,以使從rndf1輸出可以爲什麼綁定函數需要被編碼送入rndf2

  1. 因爲它是
  2. 爲什麼綁定功能不具有幼稚型
+1

您是什麼意思「我們最終要採用的正確類型」?如果你指的是'unit'的類型,那麼它是錯誤的,因爲'unit :: a - > c - >(a,c)'(注意缺少的'Num'上下文)。 – Vitus

+0

@Vitus - 我已經添加了一些額外的澄清,以說明我在談論的是什麼類型,同時澄清了我的實際問題。 從本質上來說,我的困惑與Chris Bogart從原文的評論[鏈接到這裏]相同(http://blog.sigfpe.com/2006/08/you-could-have-invented-monads-and.html?showComment= 1155160080000#c115516011657470756) – Chime

回答

0

bind應該採取兩種功能。你的「初始」類型簽名需要一個函數和一個元組,併產生一個元組。這不是真的適用於這個問題:rndf1不是一個元組,它是一個函數。一旦參數應用於它,只有這樣rndf1 a s纔會成爲一個元組。所以bind需要是一個具有兩個功能的函數。

把一個元組管理成一個帶有兩個參數的函數的神祕力量在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' 

在這個定義中,無論是fx是函數(也許,如果你改名xg這將是更清晰)。

採取let (x',seed') = x seedx seed產生一個元組。在等號的左側,該元組的各個元素被賦予新名稱,然後在下一行中這些名稱被傳遞給函數f

因此,它可能有助於打破錶達bind rndf2 . rndf1

請記住,所有函數實際上只有一個參數。可以將rndf1的類型最準確地編寫爲(Num a , Num b) => a -> (b -> (a,b))。這對理解後面的內容非常有幫助。

另一件有用的事情是想想如何使用這個表達式的參數。如:(bind rndf2 . rndf1) a s

由於所有函數都有一個參數,所以它們被逐個發送到圓括號的內部。第一個:((bind rndf2 . rndf1) a) s

現在,您可以在不存在.運算符的情況下重寫表達式:(bind rndf2 (rndf1 a)) sa傳遞給rndf1,因爲這是.的工作方式:點的右側的函數接受輸入,然後將其輸出傳遞給左側的函數。

您會看到rndf1 a的類型簽名與bind的第二個參數匹配。因此,然後應用參數rndf2(rndf1 a)bind

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

現在你有一個只需要一個seed參數括號內的功能。因此,您可以採取s並將其應用於該功能:

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

最近幾天纔想到答案。我認爲我已**幾乎得到它 - 當你說_記住所有函數實際上只有一個參數._你正在考慮'(。)'運算符的2個參數嗎? – Chime

+0

明白了! - 非常感謝 :-) – Chime