在不同的问题上,我在评论中找到了有关使用 Monad 的
(->)
实例的提示,例如实现无点风格。
对于我来说,这有点太抽象了。好吧,我在
(->)
上看到了 Arrow 实例,在我看来,(->)
可以在实例符号中使用,但不能在类型声明中使用(这本身就是另一个问题的内容)。
有没有人使用
(->)
作为 Monad 实例的例子?或者有好的链接吗?
很抱歉,如果这个问题可能已经在这里讨论过,但是搜索 “
(->)
Monad 实例” 会给你带来很多你可以想象的结果......因为几乎每个关于 Haskell 的问题都涉及 (->)
或“Monad” ”.
对于给定类型
r
,类型 r -> a
的函数可以被认为是使用类型为 a
的环境传递 r
的计算。给定两个函数 r -> a
和 a -> (r -> b)
,很容易想象在给定环境(同样,类型为 r
)时可以组合这些函数。
但是等等!这正是 monad 的意义所在!
因此,我们可以为
(->) r
创建一个 Monad 实例(这是 Haskell 语法,用于将双参数类型构造函数 (->)
部分应用到第一个类型参数 r
,即,它的含义与第二个类型参数 r -> _
类似)参数尚未给出),通过将 f >>= g
传递给 r
和 f
来实现 g
。这就是 (->) r
的 Monad 实例所做的事情。
要实际访问环境,您可以使用
id :: r -> r
,您现在可以将其视为在环境 r
中运行并交付 r
的计算。要创建本地子环境,您可以使用以下命令:
inLocalEnvironment :: (r -> r) -> (r -> a) -> (r -> a)
inLocalEnvironment xform f = \env -> f (xform env)
这种将环境传递给计算,然后可以在本地查询并修改它的模式不仅对
(->) r
monad 有用,这就是为什么它被抽象到 MonadReader
类中,使用比我在这里用过:
http://hackage.haskell.org/packages/archive/mtl/2.0.1.0/doc/html/Control-Monad-Reader-Class.html
基本上,它有两个实例:我们在这里看到的
(->) r
和 ReaderT r m
,它只是 newtype
的 r -> m a
包装器,所以它与我之前的 (->) r
monad 是一样的此处描述,但它在其他一些经过转换的 monad 中提供计算。
要为
(->) r
定义一个 monad,我们需要两个操作,return
和 (>>=)
,并遵守三个定律:
instance Monad ((->) r) where
如果我们看一下退货的签名
(->) r
return :: a -> r -> a
我们可以看到它只是常量函数,忽略了它的第二个参数。
return a r = a
或者交替地,
return = const
要构建
(>>=)
,如果我们使用 monad (->) r
来专门化它的类型签名,
(>>=) :: (r -> a) -> (a -> r -> b) -> r -> b
实际上只有一种可能的定义。
(>>=) x y z = y (x z) z
使用这个 monad 就像向每个函数传递一个额外的参数
r
。您可以使用它进行配置,或者将选项深入到程序的内部。
我们可以通过验证三个单子定律来检查它是否是一个单子:
1. return a >>= f = f a
return a >>= f
= (\b -> a) >>= f -- by definition of return
= (\x y z -> y (x z) z) (\b -> a) f -- by definition of (>>=)
= (\y z -> y ((\b -> a) z) z) f -- beta reduction
= (\z -> f ((\b -> a) z) z) -- beta reduction
= (\z -> f a z) -- beta reduction
= f a -- eta reduction
2. m >>= return = m
m >>= return
= (\x y z -> y (x z) z) m return -- definition of (>>=)
= (\y z -> y (m z) z) return -- beta reduction
= (\z -> return (m z) z) -- beta reduction
= (\z -> const (m z) z) -- definition of return
= (\z -> m z) -- definition of const
= m -- eta reduction
最终的单子定律:
3. (m >>= f) >>= g ≡ m >>= (\x -> f x >>= g)
遵循类似、简单的等式推理。
我们还可以为 ((->) r) 定义许多其他类,例如 Functor,
instance Functor ((->) r) where
如果我们看一下
的签名 -- fmap :: (a -> b) -> (r -> a) -> r -> b
我们可以看到它只是组成!
fmap = (.)
类似地,我们可以创建一个
Applicative
的实例
instance Applicative ((->) r) where
-- pure :: a -> r -> a
pure = const
-- (<*>) :: (r -> a -> b) -> (r -> a) -> r -> b
(<*>) g f r = g r (f r)
拥有这些实例的好处是它们可以让您在操作函数时使用所有 Monad 和 Applicative 组合器。
有很多涉及 (->) 的类实例,例如,您可以为 (b -> a) 手写 Monoid 的实例,给定
a
上的 Monoid 为:
enter code here
instance Monoid a => Monoid (b -> a) where
-- mempty :: Monoid a => b -> a
mempty _ = mempty
-- mappend :: Monoid a => (b -> a) -> (b -> a) -> b -> a
mappend f g b = f b `mappend` g b
但是给定 Monad/Applicative 实例,您也可以使用
定义此实例instance Monoid a => Monoid (r -> a) where
mempty = pure mempty
mappend = liftA2 mappend
使用
(->) r
或 with 的 Applicative 实例
instance Monoid a => Monoid (r -> a) where
mempty = return mempty
mappend = liftM2 mappend
使用 Monad 实例来实现
(->) r
。
这里节省的空间很小,但是,例如 #haskell IRC 频道上的 lambdabot 提供的用于生成无点代码的 @pl 工具就滥用了这些实例。