【Haskell】【翻译】操作上下文中的类型(Working with type in a context)
发现《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,就容易发现上面的“换句话说”两边的描述其实就是对fmap
的不同诠释。
(Functor能解决一种类型不匹配问题),但仍有其它三种类型不匹配问题。
applicative能解决其中两种。其中第一种情况是连接的第一部分在上下文中,而结果不在,如图8。

另一种情形则是整个函数都在上下文中。比如函数Maybe (Int -> Double)意味着这个函数本身可能不存在。这(函数被包裹在上下文中)听起来奇怪,但它很有可能发生在对Maybe
和IO
的偏调用中。图9描述了这一有趣的情形。

Map.lookup和putStrLn
的类型签名都是这样。这个问题被

Maybe,IO

下面是英文原文。



