作者monad和列表作家monad有什么区别

时间:2019-04-13 02:31:02

标签: functional-programming monads

我查看了作家monad的示例以了解其工作原理,几乎所有这些看上去都像列表作家monad。我知道列表作家monad是作家monad的一种。但是,从外行角度来讲,真正的作家莫纳德是什么。

1 个答案:

答案 0 :(得分:1)

通俗地说,writer monad是使您在生成值时将项目“写入”到“ log”的monad。完成后,最终得到的是产生的值 和包含所有编写内容的日志。换句话说,它是monad的副作用,它的作用是“将内容写入日志”。

让我们通过列表编写器和(通用)编写器单子的示例来更具体地说明这一点。我将在这里使用Haskell,因为它是描述Monads for Functional Programming的原始上下文。

列表作家Monad

我假设“列表编写器” monad是将一个项目(某种类型,我们称为w)记录到一个项目列表(当然,类型为[w])中的一个。它还会产生类型a的值。 (如果您自己使用此代码时遇到错误,请参阅底部的注释。)

newtype ListWriter w a = ListWriter { runListWriter :: ([w], a) }

instance Monad (ListWriter w) where
  return a = ListWriter ([], a)    -- produce an a, don't log anything
  ListWriter (ws, a) >>= k =
    let ListWriter (xs, a') = k a  -- run the action 'k' on the existing value,
    in ListWriter (ws ++ xs, a')   -- add anything it logs to the existing log, 
                                   -- and produce a new result value

-- Add an item to the log and produce a boring value.
-- When sequenced with >>, this will add the item to existing log.
tell :: w -> ListWriter w ()
tell w = ListWriter ([w], ())

ex1 :: ListWriter String Int
ex1 = do
  tell "foo"
  tell "bar"
  return 0

(注意:这等效于ex1 = tell "foo" >> tell "bar" >> return 0,表明将tell>>一起使用可将一个项目添加到日志中。)

如果我们在GHCi中评估runListWriter ex1,我们会发现它在日志中写入了“ foo”和“ bar”,并产生了结果值0

λ> runListWriter ex1
(["foo","bar"],0)

(一般)作家莫纳德

现在,让我们看看如何将其变成通用编写器monad。作者monad可以处理可以组合在一起的任何事物,而不仅仅是列表。具体来说,它可以与任何Monoid一起使用:

class Monoid m where
  mempty :: m            -- an empty m
  mappend :: m -> m -> m -- combine two m's into a single m

列表是一个Monoid,[](++)分别为memptymappend。 Monoid的非列表示例是整数之和:

λ> Sum 1 <> Sum 2        -- (<>) = mappend
Sum {getSum = 3}

那么作家单子就是

newtype Writer w m = Writer { runWriter :: (w, m) }

我们只有一个w,而不是w的列表。但是,当我们定义Monad时,我们确保wMonoid,因此我们可以从一个空的日志开始,然后向该日志添加一个新条目:

instance Monoid w => Monad (Writer w) where
  return a = Writer (mempty, a)   -- produce an a, don't log anything
  Writer (w, a) >>= k =
    let Writer (x, a') = k a      -- we combine the two w's rather than 
    in Writer (w <> x, a')        -- (++)-ing two lists

请注意此处的区别:我们使用mempty代替[],使用(<>)代替(++)。这就是我们从列表到任何Monoid的概括。

所以writer单子实际上是列表单子对可以组合的任意事物的概括,而不仅仅是列表。您可以将列表与Writer一起使用,以获取(几乎)等同于ListWriter的内容。唯一的不同是,当您将记录的项目附加到日志时,必须将其包装在列表中:

ex2 :: Writer [String] Int
ex2 = do
  tell ["foo"]
  tell ["bar"]
  return 0

但是您得到相同的结果:

λ>  runWriter ex2
(["foo","bar"],0)

这是因为您正在记录“列表”,而不是记录“将放入列表中的项目”。 (这确实意味着您可以通过传递一个以上元素列表来同时记录多个项目。)

有关Writer的非列表使用的示例,请考虑计算排序函数进行的比较。每次对函数进行比较时,都可以tell (Sum 1)。 (您可以告诉某人。得到它了吗?这是吗?)最后,您将获得所有比较的总数(即总和)以及排序列表。


注意:如果您尝试自己使用这些ListWriterWriter定义,GHC会告诉您缺少FunctorApplicative实例。拥有Monad实例后,您可以用其术语写其他实例:

import Control.Monad (ap, liftM)

instance Functor (ListWriter w) where
  fmap = liftM

instance Applicative (ListWriter w) where
  pure = return
  (<*>) = ap

Writer同样如此。为了清楚起见,我在上面删除了它们。