【Haskell】【翻譯】操作上下文中的類型(Working with type in a context)
發(fā)現《Get Programming with Haskell》這本書中對Functor,applicative,Monad的概念的引入非常直觀有趣,在這里進行一波翻譯。我是Haskell初學者,且英語水平也不高,所以難免拉垮,望讀者海涵并給予意見。文章最后粘貼了英文原文的圖片。
在文章中,我將type翻譯作類型,type class翻譯作類型類,但作者有時會把Maybe,IO等稱作類型,令人感嘆

在這個單元里,你將關注Haskell的三個最具威力,但同時也最迷惑人的類型類:Functor,applicative
和Monad
。這些類型類名字有趣,但其目的卻相對的明確。它們中的每一個都建立在前一個之上,并提供你在諸如
IO等上下文中進行操作的能力。在單元4里,你大量使用了Monad
類型類以操作IO
。在這個單元里,你將更深刻地理解其工作原理。為更好地感受這些抽象的類型類的行為,(在這里)你將把類型當作形狀來看待。
理解函數的一種方式是認為其將一種類型轉換成另一種類型。讓我們把兩個類型可視化為兩個形狀,一個圓和一個正方形,就如圖1所示。

這些形狀可以代表任意兩個類型,比如Int和Double
,String
和Text
,Name
和FirstName
以及其他。當你試圖將一個圓轉換成一個正方形的時候,你就在使用函數。你可以把函數可視化為兩個形狀間的一種連接(connector),如圖2所示。
譯者:顯然,這兩個形狀也可以代表同一個類型。

這個連接可以代表任何從一個類型到另一個類型的函數。它可以代表(Int -> Double),(String -> Text)
,(Name -> FirstName)
,諸如此類。當你試圖應用一個轉換時,你可以可視化地將連接器置于初始值(在當前的情形下,是一個圓形)以及期望值(一個正方形)之間;見圖3。

當每個形狀都正確匹配,你就能完成你所期望的轉換。
在這個單元,你將關注如何操作處于上下文(context)中的類型。你已經見過的兩個關于上下文中的類型的最好的例子是Maybe類型和
IO類型。
Maybe類型代表這樣一種上下文,即其中的值可能不存在;
IO類型代表著這樣一種上下文,即其中的值將同I/O交互(the value has interacted with I/O)。放到我們的可視化語言中,你可以想象上下文中的類型將像圖4這樣表述。

這些形狀可以代表諸如IO Int,IO Double
,Maybe String
,Maybe Text
,Maybe Name
,Maybe FirstName
等的類型。因為這些類型是處于一定的上下文中的,你不能用你的原有的連接去進行轉換。當前,在本書中你曾依賴過那些輸入和輸出都處在同樣的上下文中的函數。而為對上下文中的類型進行轉換,你需要一個類似圖5的連接。

這個連接代表那些類型簽名形如(Maybe Int -> Maybe Double),(IO String -> IO Text)
和(IO Name -> IO FirstName)
的函數。通過該連接,你很容易對上下文中的類型進行轉換,就如圖6所示。

這看上去像是一個完美的解決方案,但是這里有個問題。讓我們看下面這個函數halve,它的類型是
Int -> Double,其行為就如我們所期望的,對半分(halve)輸入參數Int
。
這個函數很直白,但假設你想對半分一個Maybe Int呢?僅用手頭的工具,你必須對這個函數編寫一個包裝器(wrapper)以使它能夠對
在這個例子里,寫一個簡單的包裝器并非難事。但若是對一大片的現存的a -> b函數,想要使用它們中的任意一個操作
Maybe類型都需要編寫幾乎同樣的包裝器。更糟糕的是你無法編寫對IO
類型的包裝器!
譯者:為什么無法編寫IO
的包裝器?你需要對
IO類型的實例進行解構并獲取它的值,再重新構造它,而解構這一步是無法做到的——這意味著Haskell將會提供諸如
IO Int -> Int這樣簽名的函數,這是不安全的——你不能保證函數是純函數了!假設你又有一個函數
Int -> IO Int(這是容易做到的,通過return
之類),你就可以將兩個函數組合,使其具有Int -> Int
的函數簽名,但是在內部做dirty work。當然,Haskell的確提供了這樣的unsafe函數就是了。
于是,我們的Functor,applicative和Monad
來到了!你可以認為這些類型類是適配器(adapter),它們允許你在底層(underlying)類型(圓和正方形)相同的情況下使用不同的連接(You can think of these type classes as adapters that allow you to work with different connectors so long as the underlying types (circle and square) are the same)。比如在
halve中,你關心轉換你的基本的Int
到Double
(的函數),使它能夠適配以工作在上下文的類型中。這是
Functor類型類的工作,如圖7。

譯者:這也就是說
Functor能夠使類型
a -> b的函數將
(Functor f) => f a類型轉化為
(Functor f) => f b。換句話說,Functor
能夠將a -> b
轉化成(Functor f) => f a -> f b
。
如果你曾了解過
Functor的方法(是這么叫嗎?)fmap
,查看它的簽名fmap :: (Functor f) => (a -> b) -> f a -> f b,就容易發(fā)現上面的“換句話說”兩邊的描述其實就是對fmap
的不同詮釋。
(Functor能解決一種類型不匹配問題),但仍有其它三種類型不匹配問題。
applicative能解決其中兩種。其中第一種情況是連接的第一部分在上下文中,而結果不在,如圖8。

另一種情形則是整個函數都在上下文中。比如函數Maybe (Int -> Double)意味著這個函數本身可能不存在。這(函數被包裹在上下文中)聽起來奇怪,但它很有可能發(fā)生在對Maybe
和IO
的偏調用中。圖9描述了這一有趣的情形。

Map.lookup和putStrLn
的類型簽名都是這樣。這個問題被

Maybe,IO

下面是英文原文。



