首页 文章

'Chaining operations'是Monad课程解决的“唯一”问题吗?

提问于
浏览
25

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

在阅读了很多参考文献(见下文)之后,我得出的结论是,实际上, monad class 在那里解决了 only ,但是 big and crucial ,问题: the 'chaining' of functions on types with context . 因此,着名的句子"monads are programmable semicolons" . 事实上,monad可以被视为an array of functions with helper operations .

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

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

另一方面,它是 each 个体类型实例化monad类,它解决了 each concrete problembut not merely for being a instance of Monad . 例如 Maybe 求解"how a function returns a value or an error", State 求解"how to have functions with global state", IO 求解"how to interact with the outside world",依此类推 . 所有这些类都在上下文中封装了一个值 .

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

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

因此,我猜你下次定义一个上下文数据类型T来解决问题时,如果你需要对函数的调用进行排序(在T的值上),可以考虑将T作为 Monad 的实例(如果你需要"chaining with choice",如果你可以受益来自 do 符号) . 为了确保你做得对,检查T是否满足monad laws

Then, I ask two questions to the Haskell experts:

  • A concrete question: is there any other problem that the monad class solves by ifself (leaving apart monadic classes)? If any, then, how it compares in relevance to the problem of chaining operations?

  • 一个可选的一般问题:我的结论是对的,我是否误解了什么?

参考文献

教程

StackOverflow问题与解答

2 回答

  • 22

    你're definitely on to something in the way that you'重申这一点 - 有很多事情是 Monad 的意思,而且你把它们分开了 .

    也就是说,我肯定会说链接操作不是Monads解决的主要问题 . 这可以使用简单的Functors(有一些麻烦)或使用Applicatives轻松解决 . You need to use the full monad spec when "chaining with choice". 特别是, ApplicativeMonad 之间的张力来自 Applicative 需要静态地知道副作用计算的整个结构 . Monad 可以在运行时更改该结构,从而牺牲了一些功率的可分析性 .


    为了更清楚地说明这一点, the only place you deal with a Monad but not any specific monad is if you're defining something with polymorphism constrained to be a 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] . 这应该是一个明确的指标 Monad isn't the only way to sequence things .

    同样,我们可以如下定义 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 都试图在结果上放置另一个"layer"的 f . 使用 Monad ,我们可以 join 这些图层向下,但 Applicative 太弱了 .


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

  • 7

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

    它可能与monadic绑定非常相似,但不一定完全相同(回想一下,列表绑定是 concatMap ,无论如何都存在的函数,但参数的顺序不同) . 你不会有任何共同的要求 .

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

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

相关问题