如何使用 (->) 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 -> a
和a -> (r -> b)
,很容易想象在给定环境(同样是r
类型)时可以组合它们。
但是等等!这正是 monad 的意义所在!
所以我们可以通过将r
传递给f
和g
来为(->) 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
基本上,它有两个实例:我们在这里看到的(->) r
和ReaderT 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 实例用于 (->) r
或与
instance Monoid a => Monoid (r -> a) where
mempty = return mempty
mappend = liftM2 mappend
使用 (->) r
的 Monad 实例。
这里节省的费用很少,但是,例如,#haskell IRC 频道上的 lambdabot 提供的用于生成无点代码的 @pl 工具相当多地滥用这些实例。
【讨论】:
为了清楚起见,通过使用示例:所以一个简单的函数接受一个整数并返回参数 + 20 可以这样写import Control.Applicative -- this has monad instance of (->) r\n add20 :: Int -> Int \n add20= do\n p <- id \n return $ p + 20
,感谢四位您的详细回答
嗯,(>>=) x y z = y (x r) r
的行是(>>=) x y r = y (x r) r
,我的意思是,z
应该是r
,还是我错过了什么?以上是关于如何使用 (->) Monad 实例以及对 (->) 的混淆的主要内容,如果未能解决你的问题,请参考以下文章