Haskell 這段代碼該如何理解?
isPalindrome = (==) &<*&> reverse
我能理解寫成這個樣子的
isPalindrome w = w == reverse w
我也知道
&<*&> = f (a -&> b) -&> f a -&> f b
但在這裡constructor好像不太容易看出來
確實這樣寫在通常的意義上可讀性不好, 不過既然題主想要一個直觀理解,
我覺得稍微解釋一下也還是可以達到覺得這個寫法比較直觀的.來再一起看一遍題主給出的這段isPalindrome = (==) &<*&> reverse
顯然最不好理解的是這裡的 &<*&> , 如果我們知道這裡的 &<*&> 對應的 Applicative的實例是哪個, 其實問題就解決一半了.
其實有經驗的話, 看到這裡沒有用到什麼特殊的構造, 那麼就應該往比較通用的Applicative的instance上考慮了, 查看文檔 http://hackage.haskell.org/package/base-4.9.0.0/docs/src/GHC.Base.html#Applicative 可以知道, 其實比較通用的Applicative的實例就2個, 分別是:
instance Applicative []
instance Applicative ((-&>) a)
我們再看一眼 &<*&> 的類型
(&<*&>) :: Applicative f =&> f (a -&> b) -&> f a -&> f b
那假如這裡用到的 Applicative 的 instance 是 [] 的話, &<*&> 的兩個參數就應該具有形如 [a] 的類型, 而 reverse 和 (==) 顯然不具有這樣的類型.
所以其實很容易知道這裡的 Applicative 的 instance 其實是 ((-&>) a).
至此問題已經解決一半了, 下面我們驗證一下.
把 f = (c -&>) (這裡把上面的a重命名成了c, 以及把 (-&>) 改寫成了中綴形式) 代入到 &<*&> 的類型得到specialize後的 &<*&> 的類型:
(&<*&>) :: (c -&> (a -&> b)) -&> (c -&> a) -&> (c -&> b)
又從 instance Applicative ((-&>) a) 中, 我們得到了 &<*&> 的實現
(&<*&>) f g x = f x (g x)
isPalindrome = (==) &<*&> reverse -- (1) (原式)
isPalindrome = (&<*&>) (==) reverse -- (2) (中綴形式變為前綴形式)
isPalindrome xs = ((&<*&>) (==) reverse) xs -- (3) (逆eta變換)
isPalindrome xs = (&<*&>) (==) reverse xs -- (4) (函數應用的左結合性)
isPalindrome xs = (==) xs (reverse xs) -- (5) (代入 &<*&> 的實現)
isPalindrome xs = xs == (reverse xs) -- (6) (前綴形式變為中綴形式)
好了, 到了最後一步應該就是童叟無欺, 大家都看得懂啦, 誰還看不懂舉手, 我保證不叫家長..
到此問題已經解決, 不過肯定有同學要暴躁了: 說好的"直觀"理解呢...
好的, 所以下面要開始劃重點了哦:
當我們看到以下式子的時候
(&<*&>) :: f (a -&> b) -&> f a -&> f b
我們把式子中的 "f xxx" 替換成 "xxx的半成品" 然後念出來, 於是你就發現上式念作:
(a -&> b) 的半成品 -&> a的半成品 -&> b的半成品
以及, 你會發現 &<*&> 的作用其實就是, 當缺乏的材料被給足的時候, 先把 (a -&> b)的半成品 捏合成 (a -&> b) , 把 a的半成品 捏合成 a, 然後 把 (a -&> b) 應用於 a 得到 b, 然後返回b; 當然由於以上是假設缺乏的材料被給足的時候, 能返回b, 而目前還沒有達到這個假設, 所以上面的過程返回的仍然是 b的半成品. 用一句話來說: &<*&> 的作用就是對 一個半成品函數, 和一個半成品a, 這兩個東西進行組合, 然後返回一個半成品b.
於是當你看到下式的時候,
isPalindrome = (==) &<*&> reverse
很顯然, (==) 是 (a -&> b)的半成品, reverse 是 a的半成品.
那麼這時候其實主要問題就在於: 這裡的 "半成品" 體現在哪兒?
這裡的 "半成品" 體現在哪兒, 其實就看選用 Applicative的哪個實例了.
根據上面已經說過的思路, 我們很快意識到, 這裡應該用 (c -&>) 這個實例, 於是這時候就很清楚: "半成品" 的 "半成" 就體現在 c 還沒有被應用上去.
那麼 "成品" 應該長啥樣子呢? 當然就是 c 被應用上去之後的樣子了咯. 也就是 (==) 應用上 xs 變成 (xs ==) , 以及 reverse 應用上 xs 變成 (reverse xs); 然後對這兩者做一個函數應用, 就得到了
xs == (reverse xs) , 最後別忘了, 我們剛剛借了一個 xs, 還回去那麼就是
xs -&> xs == (reverse xs)
(注意上面的 (xs ==) 雖然是個對函數的部分應用, 但是相對於 (==) 這個半成品來說, 它已經完整了)
最後的好消息是, 以上方法對 Functor, Applicative, Monad 均有效.
關於上述思路用在Monad上的例子, 可以參考這篇回答 Python中實現 (a and b or c) in xx 這種邏輯最簡潔的方式是怎樣的? , (當然其實那個回答里也可以用 Applicative 的)
在Monad的do notation裡面的 a &<- a 的寫法, 其實就可以理解為, 我們有了 a 這個半成品, 然後通過借了某些東西, 得到了一個 a 這個成品, 於是在這個do notation後續的邏輯里, 就可以直接用 a 這個成品去玩耍啦.
最後回顧一下剛才這個普通到一般人看不見的 Function Applicative Monad 的實例:
所以對於這麼好玩的Haskell大法大家是不是應該多多茲糍捏?啊,&<*&>是S Combinator,另外之前Ice還在CodeWars上找出了用(&>&>=)((&>&>=) === flip (&<*&>))寫的……
然後你使用的instance是((-&>) a):
啊!忘了,這個玩意是haddock的bug沒做α-conversion。上次 @大笨蛋千里冰封 提到和你提的同一個問題之後我發現的,然後社區現在還沒回應(Haddock generated type signature of instance methods did not do α-conversion · Issue #613 · haskell/haddock),哦,該死,我標題上還出了個語法錯…… Anyway,正確的應該是這樣的:
然後這個instance是:
instance Applicative ((-&>) a) where
pure = const
(&<*&>) f g x = f x (g x)
(為啥長或寬小於120px的圖片不能上傳?)
(哦,另外知乎的code highlighting改了?新的Haskell highlighter好好看……)
f x (g x)和x z (y z)。
然後你自己做下substitution:
(==) &<*&> reverse
==&> (x -&> f x (g x))[f --&> (==), g --&> reverse]
==&> (x -&> (==) x (reverse x))
(相信你知道(==)和reverse函數的作用)
那麼,這樣看就簡單多了,比較x和被反轉的x,就是palindrome的定義。
----
啊……原來highlighter只是在編輯器裡面換了啊……顯示出來還是原來的那種。
謝邀,既然 @霧雨魔理沙也來了,我也湊個熱鬧。
先上結論,這種方式寫代碼是很不好的,除了耗費腦細胞外,沒有其他好處。只有一種情況例外,就是這個代碼不是給人讀的時候,一般是直接給編譯器的由程序生成的代碼。這個isPalindrome = (==) &<*&> reverse表達式所蘊含的函子是((-&>) r),也就是Reader函子,類型((-&>) r) a的意義是給一個類型r的輸入,將得到一個類型a的輸出。那如何理解這個表達式呢?
首先我們有(==) :: [a] -&> [a] -&> Bool -- same as: [a] -&> ([a] -&> Bool)
reverse :: [a] -&> [a]
應用運算符&<*&>後有
(&<*&>) :: f (a -&> b) -&> f a -&> f b
(==) &<*&> reverse :: ([a] -&> ([a] -&> Bool)) -&> ([a] -&> [a]) -&> ([a] -&> Bool)
令 f = ((-&>) [a]) 則有 f ([a] -&> Bool) -&> f [a] -&> f Bool
((==) &<*&> reverse) w
-- By definition of &<*&> in instance Applicative ((-&>) r)
-- have f &<*&> g = x -&> f x (g x)
= ((==) w) (reverse w)
-- Transform prefix of (==) to infix
= w == reverse w
isPalindrome w = w == reverse w
這是最好的寫法,簡單、直觀又不費腦,強烈建議。
a -&> b 就是 (-&>) a b,也就是 ((-&>) a) b
我也來湊個熱鬧首先 在 ghci 中查看
λ&> :i &<*&>
class Functor f =&> Applicative (f :: * -&> *) where
...
(&<*&>) :: f (a -&> b) -&> f a -&> f b
...
-- Defined in 『GHC.Base』
infixl 4 &<*&>
其中已看出 f 是一種 Applicative
然後查看λ&> :i Applicative
class Functor f =&> Applicative (f :: * -&> *) where
pure :: a -&> f a
(&<*&>) :: f (a -&> b) -&> f a -&> f b
(*&>) :: f a -&> f b -&> f b
(&<*) :: f a -&> f b -&> f a
{-# MINIMAL pure, (&<*&>) #-}
-- Defined in 『GHC.Base』
instance Applicative (Either e) -- Defined in 『Data.Either』
instance Applicative [] -- Defined in 『GHC.Base』
instance Applicative Maybe -- Defined in 『GHC.Base』
instance Applicative IO -- Defined in 『GHC.Base』
instance Applicative ((-&>) a) -- Defined in 『GHC.Base』
instance Monoid a =&> Applicative ((,) a) -- Defined in 『GHC.Base』
([a] -&> [a] -&> Bool) -&> ([a] -&> [a]) -&> ([a] -&> Bool)
其中 f 的等價內容是
([a] -&>)
([a] -&> Bool)
([a])
然後 (==) 與 reverse 的類型應該是眾所周知的吧。。
(是不是有人覺得有點亂,有人覺得再說吧)(&<*&>) :: (c -&> (a -&> b)) -&> (c -&> a) -&> (c -&> b)
ff &<*&> fx = go where go x = ff x $ fx x
這麼理解
吐個槽,貌似Haskell的語法高亮除了GHC欽定的haddock好像都高亮的好奇怪根據這個理解吧,我昨晚上在被Voile鄙視智商的時候,想出了一個類似的東西。
原函數是樹狀數組的Lowbit,OIer們肯定熟知的C++的寫法是:
template&
很簡單吧。 Haskell中樸素寫法完全一樣,不過用題主那個類似的思路可以寫成這樣:
f = (..) &<*&> (0-)
然而這並不能阻止Voile鄙視我智商
函數作為Applicative,有
f &<*&> g = x -&> f x (g x)
故
isPalindrome = x -&> (==) x (reverse x)
推薦閱讀:
※設計師學編程?從五個思維訓練開始
※getter 和 setter 方法有什麼意義?
※我們為什麼需要React?
※學習編程的好方法——控制台遊戲
※git:一個本地分支可以對應多個遠程分支么?