如何使用 (->) Monad 实例以及对 (->) 的混淆

Posted

技术标签:

【中文标题】如何使用 (->) Monad 实例以及对 (->) 的混淆【英文标题】:How to use (->) instances of Monad and confusion about (->) 【发布时间】:2011-03-15 10:09:37 【问题描述】:

在不同的问题上,我在 cmets 中发现了有关使用 Monads 的 (->) 实例的提示,例如用于实现无点样式。

对我来说,这有点太抽象了。好的,我在 (->) 上看到了 Arrow 实例,在我看来,(->) 可以用在实例表示法中,但不能用在类型声明中(这对于另一个问题来说是单独的东西)。

有没有使用(->) 作为 Monad 实例的示例?还是一个好的链接?

对不起,如果这个问题可能已经在这里讨论过,但是搜索 "(->) Monad instance" 会给你很多你可以想象的结果......因为几乎所有关于 Haskell 的问题都在某个地方涉及(->) 或“Monad”。

【问题讨论】:

你是什么意思“那个 (->) 可以用在实例符号中,但不能用在类型声明中”?你可以这样做:type F a b = (->) a b 和这个f :: (->) a b (->) 是一种类型构造器 * -> * -> *; Monad 必须是类型 * -> * 的类型构造器(如 Maybe、[]、IO、...) - 所以尝试用 (->) 实例化 Monad 会产生一种错误!但是你可以使 (-> a) Monad 的一个实例! -- 甚至 (a ->) 也可能是 Monad? 哦,谢谢你的提示!所以我可以将(->) a b 理解为a->b,对吗?所以,(->) 是类型中的运算符。 是的:(->) 是类型之上的函数——通常称为“类型构造函数”或仅称为“构造函数”——期望两种类型作为参数并产生一个函数类型作为结果。跨度> @phynfo:实际上,(r ->) 是一个 monad,而 (-> r) 不是,因为您在实现 return :: a -> (a -> r) 时遇到了问题! 【参考方案1】:

对于给定类型r,类型r -> a 的函数可以被认为是使用类型为r 的环境提供a 的计算。给定两个函数r -> aa -> (r -> b),很容易想象在给定环境(同样是r 类型)时可以组合它们。

但是等等!这正是 monad 的意义所在!

所以我们可以通过将r 传递给fg 来为(->) r 创建一个实现f >>= g 的Monad 实例。这就是 (->) 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

基本上,它有两个实例:我们在这里看到的(->) rReaderT r m,它只是r -> m a 的一个newtype 包装器,所以它与(->) r monad I 相同'已经在这里描述过,除了它在其他一些转换后的 monad 中提供计算。

【讨论】:

很好的答案,希望看到一个在这个实例中使用一些 monad 组合子的例子以及它是如何展开的。例如。 liftM2. 我还想补充一点,Monad.Reader #17 包含一篇关于 Reader Monad 的文章。在这里阅读:themonadreader.wordpress.com/2011/01/09/issue-17【参考方案2】:

要为(->) r 定义一个monad,我们需要两个操作,return(>>=),遵守三个定律:

instance Monad ((->) r) where

如果我们查看(->) r的return签名

    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

将 Applicative 实例用于 (-&gt;) r 或与

instance Monoid a => Monoid (r -> a) where
    mempty = return mempty
    mappend = liftM2 mappend

使用 (-&gt;) r 的 Monad 实例。

这里节省的费用很少,但是,例如,#haskell IRC 频道上的 lambdabot 提供的用于生成无点代码的 @pl 工具相当多地滥用这些实例。

【讨论】:

为了清楚起见,通过使用示例:所以一个简单的函数接受一个整数并返回参数 + 20 可以这样写 import Control.Applicative -- this has monad instance of (-&gt;) r\n add20 :: Int -&gt; Int \n add20= do\n p &lt;- id \n return $ p + 20 ,感谢四位您的详细回答 嗯,(&gt;&gt;=) x y z = y (x r) r 的行是(&gt;&gt;=) x y r = y (x r) r,我的意思是,z 应该是r,还是我错过了什么?

以上是关于如何使用 (->) Monad 实例以及对 (->) 的混淆的主要内容,如果未能解决你的问题,请参考以下文章

如何将多个 monad 绑定在一起?

尝试使用持久性时没有 Control.Monad.Logger.MonadLogger 的实例

如何使用 Monad.Writer 进行跟踪?

理解 Monad 变形金刚的困难

MonoidK和Monad的关系

Chaining State Monad