簡體   English   中英

Haskell`回文=反向>>=(==)`

[英]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 ~ xm ~ (->) xb ~ 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

這里mareverse function 而m里面的a是反向列表。 (==)(a -> mb)並且mba -> Bool 所以>>=返回一個mb ,它只是一個 function ,它接受一個列表並返回一個Bool 這是一致的,因為我們在 function monad 中,所以它需要一個 monad(函數)並返回另一個。 只有當我們用一個列表調用返回的那個時,整個機制才會運行。 mamb都應用於提供的列表。

手動實現(->) 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM