“链接操作”是Monad课程解决的“唯一”问题吗?

时间:2014-01-21 14:50:39

标签: haskell functional-programming monads

澄清问题:它是关于monad类型类的优点(而不是它没有统一类的实例)。

阅读了很多参考文献(见下文), 我得出的结论是,实际上, monad class 是解决唯一的问题,但重要且关键,问题:具有上下文的类型的“链接”功能。因此,着名的句子“monad是可编程的分号”。 事实上,monad可以被视为an array of functions with helper operations

我坚持monad class 之间的区别,被理解为其他类型的通用接口;以及这些实例化类的其他类型(因此,“monadic types”)。

我理解monad类本身只解决了运算符的链接,因为主要是它只强制它的类型实例 拥有bind >>=return,并告诉我们他们必须如何表现。作为奖励,编译器非常有助于为monadic类型提供do符号的编码。

另一方面, 每个个别类型实例化monad类,它解决了每个具体问题,但不仅仅是作为Monad的实例。例如Maybe解决“函数如何返回值或错误”,State解决了“如何使用全局状态的函数”,IO解决了“如何与外部世界交互” “, 等等。所有这些类都在上下文中封装了一个值。

但不久之后,我们需要对这些上下文类型进行链接操作。即,我们需要按特定顺序组织对这些类型的函数的调用(有关此类问题的示例,请阅读You could have invented monads中有关多值函数的示例)。

如果每个类型都是monad类的实例,那么你就解决了链接的问题。 要使链接起作用,您需要>>=只需要具有确切的签名,而不是其他。 (见this question)。

因此,我猜你下次定义上下文数据类型T来解决问题时,如果你需要对函数调用进行排序(在T的值上),可以考虑将T作为Monad的实例(如果你需要“选择链接”并且如果您可以从do符号中受益。为确保您做得正确,请检查T是否满足monad laws

然后,我向Haskell专家提出两个问题:

  1. 一个具体的问题:monad类是否有任何其他问题解决(离开monadic类)?那么,它与链接操作问题的相关性如何?
  2. 一个可选的一般性问题:我的结论是正确的,我是否误解了什么?
  3. 参考

    教程

    StackOverflow问题&答案

2 个答案:

答案 0 :(得分:22)

你肯定会采用某种方式来表达这一点 - Monad意味着很多事情,你已经把它们分开了。

那就是说,我肯定会说链接操作不是 Monads解决的主要问题。这可以使用简单的Functors(有一些麻烦)或使用Applicatives轻松解决。 在“选择链接”时需要使用完整的monad规范。特别是ApplicativeMonad之间的张力来自Applicative需要知道的静态的副作用计算的整个结构。 Monad可以在运行时更改该结构,从而牺牲了一些可分析性。


为了更清楚地说明这一点,你唯一一个处理Monad而不是任何特定的 monad的地方就是你定义的多态性被限制为Monad 。这在Control.Monad模块中反复出现,因此我们可以从中检查一些示例。

sequence     :: [m a] -> m [a]
forever      :: m a   -> m b
foldM        :: (a -> b -> m a) -> a -> [b] -> m a

我们可以立即拒绝sequence Monad,因为Data.Traversable sequenceA中有相应的函数,其类型略高于{Applicative f => [f a] -> f [a] 1}}。这应该是一个明确的指标, Monad不是排序事物的唯一方法

同样,我们可以按如下方式定义foreverA

foreverA :: Applicative f => f a -> f b
foreverA f = flip const <$> f <*> foreverA f

有更多方法可以对非Monad类型进行排序。但是我们遇到了foldM

的问题
foldM             :: (Monad m) => (a -> b -> m a) -> a -> [b] -> m a
foldM _ a []      =  return a
foldM f a (x:xs)  =  f a x >>= \fax -> foldM f fax xs

如果我们尝试将此定义翻译为Applicative样式,我们可能会写

foldA             :: (Applicative f) => (a -> b -> f a) -> a -> [b] -> f a
foldA _ a []      =  pure a
foldA f a (x:xs)  = foldA f <$> f a x <*> xs

但Haskell会理所当然地抱怨这不是类型检查 - 每次递归调用foldA都试图在结果上放置f的另一个“层”。使用Monad,我们可以join这些图层向下,但Applicative太弱了。


那么这如何转化为Applicative限制我们运行时选择?嗯,这正是我们用foldM表示的,一个monadic计算(a -> b -> m a)依赖于它的a参数,这是先前monadic计算的结果。这种事情在Applicative更纯粹的顺序世界中没有任何意义。

答案 1 :(得分:8)

为了解决在单个monadic类型上链接操作的问题,根本不需要使它成为Monad的实例并确保满足mo​​nad定律。您可以直接在您的类型上实现链接操作。

它可能与monadic绑定非常相似,但不一定完全相同(回想一下,列表的绑定是concatMap,一个无论如何都存在的函数,但是参数的顺序不同)。而且您不必担心monad法则,因为每种类型的界面都会略有不同,因此它们没有任何共同的要求。

要问Monad类型类本身解决了什么问题,请查看处理中任何值的所有函数(在Control.Monad和其他位置) monadic类型。解决的问题是代码重用! Monad正是所有monadic类型的一部分,每个monadic类型对于它们中的每一个都是通用的。该部分足以编写有用的计算。所有这些函数都可以实现任何单个monadic类型(通常更直接),但是已经已经为所有 monadic类型实现了 ,甚至那些尚不存在的。

您没有编写Monad实例,因此您可以对您的类型进行链接操作(实际上您通常已经有了链接方式)。您为Monad实例自动附带的所有代码编写Monad实例。 Monad不是为任何类型解决任何问题,它是一种将多种不同类型视为单一统一概念的实例。