读者monad可以做什么,应用功能不能?

问题描述 投票:7回答:2

阅读http://learnyouahaskell.com/functors-applicative-functors-and-monoids#applicative-functors后,我可以提供一个使用函数作为applicative functor的示例:

让我们说res是4个参数的函数,fafbfcfd都是采用单个参数的函数。然后,如果我没有弄错,这个应用表达式:

f <$> fa <*> fb <*> fc <*> fd $ x

与这种非奇特的表达方式相同:

f (fa x) (fb x) (fc x) (fd x)

啊。花了我很多时间来理解为什么会这样,但是 - 借助一张纸和我的笔记 - 我应该能够证明这一点。

然后我读了http://learnyouahaskell.com/for-a-few-monads-more#reader。我们又回到了这个东西,这次是monadic语法:

do
    a <- fa
    b <- fb
    c <- fc
    d <- fd
    return (f a b c d)

虽然我需要另外一张A4纸币来证明这一点,但我现在非常有信心,这同样意味着:

    f (fa x) (fb x) (fc x) (fd x)

我糊涂了。为什么?这有什么用?

或者,更确切地说:在我看来,我只是将函数的功能复制为应用程序,但使用了更详细的语法。

那么,你能给我一个例子吗?读者monad可以做那个功能,因为应用程序不能吗?

实际上,我还想问一下这两个中的任何一个是什么:应用函数或者读者monad - 因为虽然能够将相同的参数应用于四个函数(fafbfcfd)而不重复这个参数四时间确实减少了一些重复性,我不确定这种微小的改进是否证明了这种复杂程度;我想,我必须遗漏一些突出的东西;但这值得一个单独的问题

haskell monads applicative reader-monad
2个回答
8
投票

monadic版本允许您在对上下文中找到的函数的调用之间添加额外的逻辑,甚至决定不调用它们。

do
    a <- fa
    if a == 3 
      then  return (f a 1 1 1)
      else  do
          b <- fb
          c <- fc
          d <- fd
          return (f a b c d)

在你原来的do表达式中,你没有做任何Applicative实例无法做的事情,事实上,编译器可以确定这一点。如果你使用ApplicativeDo扩展,那么

do
    a <- fa
    b <- fb
    c <- fc
    d <- fd
    return (f a b c d)

确实desugar到f <$> fa <*> fb <*> fc <*> fd而不是fa >>= \a -> fb >>= \b -> fc >>= \c -> fd >>= \d -> return (f a b c d)


例如,这也适用于其他类型

  • Maybef <$> (Just 3) <*> (Just 5) == Just (f 3 5) == do x <- Just 3 y <- Just 5 return (f 3 5)
  • []f <$> [1,2] <*> [3,4] == [f 1 3, f 1 4, f 2 3, f 2 4] == do x <- [1,2] y <- [3,4] return (f x y)

4
投票

在讨论关于Reader的主要问题之前,我将首先介绍一下关于applicative-versus-monad的一些评论。虽然这种适用的风格表达......

g <$> fa <*> fb

......确实相当于这个做块...

do
    x <- fa
    y <- fb
    return (g x y)

...从Applicative切换到Monad可以根据其他计算的结果决定执行哪些计算,或者换句话说,具有依赖于先前结果的效果(参见chepner's answer):

do
    x <- fa
    y <- if x >= 0 then fb else fc
    return (g x y)

虽然MonadApplicative更强大,但我建议不要把它当作一个比另一个更有用的东西。首先,因为有一些不是monad的应用函子;其次,因为不使用比实际需要更多的功率往往会使事情变得更简单。 (此外,这种简单性有时会带来实实在在的好处,例如an easier time dealing with concurrency。)


一个括号内注:当涉及到应用对抗monad时,Reader是一个特殊情况,因为ApplicativeMonad实例happen to be equivalent。对于函数函子(即((->) r),它是没有newtype包装器的Reader r),我们有m >>= f = flip f <*> m。这意味着如果采取我上面写的第二个do-block(或者在chepner的答案中类似的那个)并且假设使用的monad是Reader,我们可以将它转换为applicative样式。


尽管如此,由于Reader最终是如此简单,我们为什么还要在这个具体案例中烦恼上述任何一个?这里有一些建议。

首先,Haskellers经常对裸函数函子((->) r)持谨慎态度,这是非常可以理解的:与直接应用函数的“非奇特表达式”相比,它很容易导致不必要的神秘代码。尽管如此,在一些选择的案例中,它可以很方便地使用。举个简单的例子,考虑Data.Char的这两个函数:

isUpper :: Char -> Bool
isDigit :: Char -> Bool

现在假设我们要编写一个函数来检查字符是大写字母还是ASCII数字。直截了当的做法是:

\c -> isUpper c && isDigit c

但是,使用应用程序样式,我们可以根据这两个函数立即编写它 - 或者,我倾向于说,这两个属性 - 而不必注意最终参数的位置:

(&&) <$> isUpper <*> isDigit

有一个像这个一样小的例子,是否以这种方式写它并不是什么大问题,而且很大程度上取决于品味 - 我非常喜欢它;别人受不了。但问题是,有时候我们并不特别担心某个价值是一个函数,因为我们碰巧将其视为其他东西 - 在这种情况下,作为一种财产 - 以及它最终的事实函数在我们看来只是一个实现细节。

这个透视转换的一个非常引人注目的例子涉及应用程序范围的配置参数:如果程序的某个层中的每个函数都以一些Config值作为参数,那么您很可能会发现将其作为背景假设的可用性更加舒适,而不是比在任何地方明确传递它。事实证明,这是读者monad的主要用例。


无论如何,你对Reader有用性的怀疑至少在某种程度上得到了证明。事实证明,Reader本身,功能 - 但是包装在花式新型仿函数中,实际上并没有经常在野外使用。最常见的是包含Reader功能的monadic堆栈,通常通过ReaderT和/或MonadReader类。讨论monad变换器的长度对于这个答案的空间来说太过分了,所以我要注意你可以使用ReaderT r IO,就像你使用Reader r一样,除了你还可以在IO计算中滑动方式。在ReaderT上看到一些IO变体作为Haskell应用程序外层的核心类型并不罕见。


最后一点,你可能会发现有趣的是看看joinControl.Monad为函数functor做了什么,然后弄清楚为什么这有意义。 (解决方案可以在this Q&A找到。)

© www.soinside.com 2019 - 2024. All rights reserved.