让我们以两种方式一步一步地查看您的示例。首先,让我们看看 do 符号等价物是什么:
do
a <- (+ 2)
b <- (+ 3)
return (a + b)
这本质上是对以下重复模式的抽象,其中许多不同的函数都应用于相同的共享“环境”值,然后返回一些最终结果:
\ env -> let
a = (+ 2) env
b = (+ 3) env
in (a + b)
实际上,为了与其他动作保持一致,return 也接收环境,而只是忽略它:
\ env -> let
a = (+ 2) env
b = (+ 3) env
in const (a + b) env -- const x y = x
我们可以通过让>>= 携带此环境并将其传递给每个函数((+ 2)、(+ 3)、const (a + b))来使其适合Monad 模式。
>>= 有两个函数function 和continue,其中function 是上面的(+ 2) 或(+ 3) 之一; >>= 将function 应用于环境,然后将两件事传递给continue:function 的结果,以及再次环境,以便continue 中的后续步骤也可以读取环境。
-- Type:
(>>=) :: (Monad m) => m a -> (a -> m b) -> m b
-- or:
(>>=) :: (->) env a -> (a -> (->) env b) -> (->) env b
-- or:
(>>=) :: (env -> a) -> (a -> env -> b) -> env -> b
-- Implementation:
instance Monad ((->) env) where
function >>= continue = \ env -> continue (function env) env
-- or:
(>>=) = \ function continue env -> continue (function env) env
事实上,使用{-# LANGUAGE InstanceSigs #-} 扩展,您可以将上面的最后一个类型准确地写为>>= 在Monad 实例中的签名,我建议使用该扩展来帮助您实现类型类学习。
return 接受一个值x,并生成一个函数来代替上面的continue,正如我之前提到的,它只是忽略环境并返回x。
return x = \ _env -> x
-- =
return = \ x _env -> x
-- =
return = const
其次,如果我们回到去糖的do 表示法,并添加括号以阐明 lambda 的范围:
(+ 2) >>= (\ a -> (+ 3) >>= (\ b -> return (a + b)))
然后我们可以内联>>= 和return 的定义,看看它们在这种情况下是如何工作的。我已经对每个替换中的变量进行了编号,以使范围更加明确。
(\ function1 continue1 env1 -> continue1 (function1 env1) env1)
(+ 2)
(\ a ->
(\ function2 continue2 env2 -> continue2 (function2 env2) env2)
(+ 3)
(\ b ->
(\ x env3 -> x)
(a + b)))
然后我们可以通过将函数应用到它们的参数来替换所有变量(beta-reduction):
let
function1 = (+ 2)
continue1 = \ a ->
let
function2 = (+ 3)
continue2 = \ b ->
let
x = (a + b)
in \ _env3 -> x
in \ env2 -> continue2 (function2 env2) env2
in \ env1 -> continue1 (function1 env1) env1)
-- =
\ env1 ->
(\ a env2 ->
(\ b _env3 -> a + b)
(env2 + 3)
env2)
(env1 + 2)
env1
-- =
\ env1 -> let
a = (env1 + 2)
env2 = env1
in let
b = (env2 + 3)
_env3 = env2
in (a + b)
那么我们可以省略所有多余的env变量,因为它们都是相等的,只是用来将值传递给所有函数:
\ env -> let
a = (env + 2)
b = (env + 3)
in (a + b)
这正是我们试图抽象出来的代码!
不过,这只是一个Monad 实例;所有其他实例的工作方式根据所涉及的特定类型而有所不同。所有基本实例的共同点是它们在纯代码中抽象了一些常见的重复模式,例如:
-- State:
\ state1 -> let
(a, state2) = function1 state1
(b, state3) = function2 state2
(c, state4) = function3 state3
…
in (x, stateX)
-- Writer:
\ log1 -> let
(a, log2) = function1
(b, log3) = function2
(c, log4) = function3
…
in (x, log1 <> log2 <> log3 <> log4)
-- Maybe:
case function1 of
Just a -> case function2 of
Just b -> case function3 of
Just c -> …
Just x -> Just x
Nothing -> Nothing
Nothing -> Nothing
Nothing -> Nothing
Nothing -> Nothing
-- Either:
case function1 of
Right a -> case function2 of
Right b -> case function3 of
Right c -> …
Right x -> Right x
Left errorX -> Left errorX
Left error3 -> Left error3
Left error2 -> Left error2
Left error1 -> Left error1
-- List:
concatMap
(\ a -> concatMap
(\ b -> concatMap
(\ c -> concatMap
…
[x])
function3)
function2)
function1
同样,基本的 monad transformers 抽象了 monadic 代码中的这些模式。
-- MaybeT:
do
ma <- function1
case ma of
Just a -> do
mb <- function2
case mb of
Just b -> do
mc <- function3
case mc of
Just c -> …
Just x -> pure (Just x)
Nothing -> pure Nothing
Nothing -> pure Nothing
Nothing -> pure Nothing
Nothing -> pure Nothing
Nothing -> pure Nothing
Monad/Applicative/Functor 层次结构为数据结构和控制结构提供了一种通用方法,可以抽象出像这样的某种顺序模式,因此它们可以全部与do 符号和库函数,如 replicateM,适用于 any Monad。
查看很多个不同的例子,而不仅仅是一个例子,有助于了解它们的共同点和不同之处。