[英]Haskell `palindrome = reverse >>= (==)`
有人可以解釋一下 function 中 Haskell 的回文是如何工作的:
palindrome :: Eq a => [a] -> Bool
palindrome = reverse >>= (==)
-- type declarations
reverse :: [a] -> [a]
>>= :: Monad m => m a -> (a -> m b) -> m b
(reverse >>=) :: ([a] -> [a] -> b) -> [a] -> b
(==) :: Eq a => a -> a -> Bool
特別是,函數上的 Monad 類型類的定義如何工作,以及它如何以某種方式將 (==) 的輸入數量從兩個列表減少到一個列表?
這有點令人興奮,所以請注意:-)
monad 是一個類型構造函數,它接受一個類型參數。 例如, Maybe
就是這樣一個類型構造函數。 所以, Maybe
是一個 monad,然后Maybe Int
是那個 monad 中的一個 monadic 值。 同樣, IO
是一個單子,然后IO Int
是該單子中的單子值。
現在,也可以從具有兩個類型參數的類型構造函數中創建一個 monad。 為此,我們只需要修復第一個。 例如,查看Either
:它有兩個參數,但 monad 必須只有一個。 所以我們固定第一個參數。 因此, Either Bool
是一個 monad,然后Either Bool Int
是該 monad 中的一個 monadic 值。 類似地, Either String
是一個完全不同的 monad,然后Either String Int
是該 monad 中的一個 monadic 值。
另一方面,看看函數是如何表示的:
a -> b
但這只是中綴運算符的詭計,類似於5 + 42
或"foo" <> "bar"
。 這個中綴符號可以像這樣“規范化”:
(->) a b
所以真的,“函數”可以看作是一個有兩個類型參數的類型構造函數,就像Either
一樣。 對於“函數”構造函數來說,這些參數具有特定的含義——一個是“函數輸入”,另一個是“函數輸出”。
好的,現在我們已經准備好將函數視為 monad。 就像Either
一樣,為了做到這一點,我們必須修復第一個類型參數。 因此,例如, (->) Bool
是一個單子,然后(->) Bool Int
(也稱為Bool -> Int
)是該單子中的單子值。 類似地, (->) String
是一個完全不同的 monad,然后(->) String Int
(也稱為String -> Int
)是該 monad 中的一元值。
給你一個直覺,看待它的一種方式是,這種 monad 中的 monadic 值意味着“承諾”——即“你給我一個String
,我給你一個Int
返回”。 然后標准的一元組合讓您可以將這些承諾組合在一起,就像您組合IO
動作一樣。
跟我到現在? 好的,很好。
現在讓我們看看如何實現綁定(又名>>=
)。 >>=
的簽名如下:
(>>=) :: m a -> (a -> m b) -> m b
現在讓我們將其專門用於函數。 現在,假設m ~ (->) Bool
。 然后我們有:
(>>=) :: (->) Bool a -> (a -> (->) Bool b) -> (->) Bool b
或者,如果我們以中綴形式重寫(->)
構造函數,我們得到:
(>>=) :: (Bool -> a) -> (a -> (Bool -> b)) -> (Bool -> b)
所以你看 - 綁定接受“ a
的承諾”,然后是 function 從a
到“ b
的承諾”,然后返回“ b
的承諾”。 那么,實現是微不足道的:
(>>=) :: (Bool -> a) -> (a -> (Bool -> b)) -> (Bool -> b)
(>>=) promiseOfA f = \theBool -> f (promiseOfA theBool) theBool
這里我們創建一個新的“promise”,它是一個帶Bool
的 function ,這個“promise”所做的是將給定的Bool
傳遞給“promise of a
”,然后將得到a
傳遞給 function f
,它返回 a “promise of b
”,然后我們將相同的Bool
傳遞給它,最終獲得結果b
。
注意這里:看看theBool
是如何使用兩次的——首先傳遞給promiseOfA
,然后再次傳遞給f
的結果? 這就是“減少參數數量”發生的地方。 這是我們兩次傳遞參數的地方。
但是,當然,這不僅僅適用於Bool
s。 任何輸入類型都是公平的游戲。 所以我們可以這樣概括:
(>>=) :: (input -> a) -> (a -> (input -> b)) -> (input -> b)
(>>=) promiseOfA f = \i -> f (promiseOfA i) i
(與標准庫中的實際定義進行比較)。
呸!
好的,現在我們終於准備好查看您的原始示例了。 首先,看reverse
。 由於它是 function,我們可以將其視為 monad (->) [a]
中的一元值 - 即“ [a]
的承諾”,其中“輸入”也是[a]
。
然后, (==)
的簽名:
(==) :: Eq x => x -> x -> Bool
(請注意,我故意將a
替換為x
:不要將其與a
from reverse
的簽名混淆 - 它們是兩個不同a
,不必是相同的類型)
我們可以將此簽名視為一個 function ,它接受一個x
並返回另一個x -> Bool
類型的 function 。 所以:
(==) :: x -> (x -> Bool)
這可以看作是 bind 的第二個參數,即a -> mb
類型的參數。 為了這樣看,我們需要說a ~ x
和m ~ (->) x
和b ~ Bool
。 所以這里討論的單子是(->) x
- 即輸入類型為x
的“承諾”。
可是等等! 為了將此 function 綁定到reverse
,它們需要在同一個 monad 中! 這意味着x ~ [a]
。 這反過來意味着(==)
的類型被固定到:
(==) :: [a] -> ([a] -> Bool)
因此,當我們調用(>>=)
將其reverse
作為第一個參數並將(==)
作為第二個參數時,我們得到:
reverse >>= (==)
= \i -> (==) (reverse i) i -- by my definition above
= \i -> reverse i == i
我認為這將是對我之前的答案的擴展,我在其中解釋了 function 如何成為仿函數和應用程序。 讓我從同一句話開始。
我們可以將函數( (->) r
)視為具有包含值的上下文,一旦應用就會顯示出來。 在這里,我們有reverse
function ,其中包含一個反向列表,但直到我們將其應用於列表時才得到它。 讓我們記住 bind 的類型簽名。
>>= :: Monad m => m a -> (a -> m b) -> m b
這里ma
是reverse
function 而m
里面的a
是反向列表。 (==)
是(a -> mb)
並且mb
是a -> Bool
。 所以>>=
返回一個mb
,它只是一個 function ,它接受一個列表並返回一個Bool
。 這是一致的,因為我們在 function monad 中,所以它需要一個 monad(函數)並返回另一個。 只有當我們用一個列表調用返回的那個時,整個機制才會運行。 ma
和mb
都應用於提供的列表。
手動實現(->) r
的單子實例就像
return x = \_ -> x -- aka const
f >>= g = \r -> g (f r) r
如果我們仔細研究,我們會注意到>>=
實際上是<*>
的翻轉版本,用於(->) r
類型。 但是當我說翻轉它並不意味着g <*> f
== f >>= g
時要小心。
g <*> f = \x -> g x (f x)
f >>= g = \x -> g (f x) x
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.