所有Monads之母的文章提供了以下代码:
ex3 = do
a <- return 1
b <- ContT (fred -> "escape")
return $ a+b
然后我可以执行它:
ghci> runContT ex3 show
"escape"
但我不能跑:
ghci> runContT ex3 id
<interactive>:51:14:
Couldn't match type ‘[Char]’ with ‘Integer’
Expected type: Integer -> [Char]
Actual type: Integer -> Integer
In the second argument of ‘runContT’, namely ‘id’
In the expression: runContT ex3 id
return $ a+b
如何进行类型检查?
runCont ex3 show
是怎么回事?runCont ex3 id
怎么样——看起来不能Int + [Char]
——但它为什么要编译?
首先,让我们看看Cont
是如何定义的:
data Cont r a = Cont {runCont :: (a -> r) -> r}
如果放大此声明,您将看到Cont
包装了一个函数,给定一个函数(a->r)
产生r
.换句话说,它"隐藏"了类型a
(或r
)的值"内部"runCont
。所以do
表达式中Cont (fred -> "escape")
(fred
是一个函数,被忽略)的存在告诉我们r
是String
的,整个表达式的runCont
是固定的返回一个String
类型的值,并且只能接受a->String
类型的函数对于某些a
, 我们需要解决这个问题。
现在让我们看看return
是什么样的:
instance Monad (Cont r) where
return x = Cont ($ x) -- given a function, pass x to it
(Cont m) >>= f = -- f :: a -> Cont r b, which roughly means
-- f :: a -> (b->r) -> r
-- which would roughly be Cont (g -> m (flip f g))
-- notice we pass f to m: f is the stuff that needs
-- executing when m needs to; f is continuation of m
let h g x = let (Cont n) = f x -- unwrap (b->r)->r
in n g
in Cont (m . h)
-- or Cont (g -> m (($ g) . runCont . f)), if that's easier to read
请注意>>=
的工作原理。如果m
不使用传递给它的函数 - 记住,runCont
可以直接"隐藏"类型r
的值,不一定是类型a
的值 - 那么"延续"(fred
Cont (fred -> "escape")
)不会被调用,你将观察到"escape"。
所以,a <- return 1
的意思是a :: Integer
,显然,b <- Cont (_ -> "escape")
并不意味着b :: String
- 相反,b
可以是任何类型的 - 传递给Cont
的函数fred
被忽略,因此任何返回String
的函数都将起作用 - 但b
的类型由表达式的其余部分固定。 return $ a + b
的意思是Cont String Integer
- 因为a
是Integer
的,所以b
也是固定的Integer
。
另外,请注意,根据定义,runCont ex3 show
中的 show
注定是 do
表达式最后一行的延续:它用于行return $ a+b
,所以你意味着传递类型 Integer -> r
的函数,因为a+b
是Integer
的,而你打算传递类型 a -> String
的函数,因为r
是由表达式 b <- Cont (_ -> "escape")
固定的。
然后,整个表达式等效于以下内容:
do
a <- return 1
b <- Cont (_ -> "escape")
return $ (a+b)
==
return 1 >>= (a -> (Cont (_ -> "escape") >>= (b -> return (a+b))))
== -- apply return rule
Cont ($ 1) >>= (a -> (Cont (_ -> "escape") >>=
(b -> Cont ($ (a+b)))))
== -- apply >>= rule
Cont (g -> ($ 1) (($ g) . runCont . (a -> (Cont (_ -> "escape") >>=
(b -> Cont ($ (a+b)))))))
== -- apply >>= rule
Cont (g -> ($ 1) (($ g) . runCont . (a -> (Cont (h ->
(_ -> "escape") (($ h) . runCont . (b -> Cont ($ (a+b)))))))))
== -- (_ -> x) y == x
Cont (g -> ($ 1) (($ g) . runCont . (a -> (Cont (h -> "escape")))))
== -- marking unused variables with "_" for clarity
Cont (g -> ($ 1) (($ g) . runCont . (_ -> (Cont (_ -> "escape")))))
== -- ($ y) (_ -> x) == x
Cont (g -> ($ g) $ runCont (Cont (_ -> "escape")))
== -- runCont (Cont x) == x
Cont (g -> ($ g) (_ -> "escape"))
== -- ($ y) (_ -> x) == x
Cont (_ -> "escape")