一开始可能有点令人困惑,但需要记住的一个重要概念是(->)
不是 monad 或函子,但是(->) r
is. Monad
and Functor
类型都有种类* -> *
,所以他们只期望一个类型参数。
这意味着什么fmap
for (->) r
好像
fmap g func = \x -> g (func x)
这也被称为
fmap g func = g . func
这只是正常的函数组合!当你fmap g
over func
,你改变输出类型通过应用g
到它。在这种情况下,如果func
有类型a -> b
, g
必须有一个类似的类型b -> c
.
The Monad
实例更有趣一些。它允许您在函数应用程序发生“之前”使用该函数应用程序的结果。帮助我理解的是看到这样的例子
f :: Double -> (Double,Double)
f = do
x1 <- (2*)
x2 <- (2+)
return (x1, x2)
> f 1.0
(2.0, 3.0)
其作用是将隐式参数应用于f
绑定右侧的每个函数。所以如果你传入1.0
to f
,它将绑定值2 * 1.0
to x1
并绑定2 + 1.0
to x2
,然后返回(x1, x2)
。它确实使得将单个参数应用于多个子表达式变得很容易。这个函数相当于
f' x = (2 * x, 2 + x)
为什么这有用?一种常见的用途是Reader
monad,它只是一个新型包装器(->) r
. The Reader
monad 可以轻松地在应用程序中应用静态全局配置。你可以写这样的代码
myApp :: Reader Config ()
myApp = do
config <- ask
-- Use config here
return ()
然后你运行你的应用程序runReader myApp initialConfig
。您可以轻松地在Reader Config
monad,组合它们,将它们链接在一起,并且它们都可以访问全局只读配置。除此之外,还有一个同伴ReaderT
monad 变压器,它允许您将其构建到变压器堆栈中,让您拥有可以轻松访问静态配置的非常复杂的应用程序。