作者单子(Writer Monad)和状态单子(State Monad)实际上是相同的吗?

22

这里有一个很棒的教程(链接),它似乎表明Writer Monad基本上是一个特殊情况的元组对象,代表(A,B)。Writer在左侧(即A)累积值,并且A有相应的Monoid(因此可以累积或修改状态)。如果A是一个集合,则它会进行累积。

State Monad也处理内部元组的对象。它们都可以进行flatMap、map等操作。对我来说,操作似乎是相同的。它们有什么不同之处?(请用Scala示例回复,我不熟悉Haskell)。谢谢!

2个回答

40
这两个单子相互关联的直觉是正确的,区别在于Writer更加受限,因为它不允许您读取积累的状态(直到您在最后一刻才能查看)。在Writer中,您可以对状态执行的唯一操作是将更多的内容添加到末尾。
更简明地说,State[S, A]S => (S, A)的一种包装,而Writer[W, A](W, A)的一种包装。
考虑以下对Writer的使用:
import scalaz._, Scalaz._

def addW(x: Int, y: Int): Writer[List[String], Int] =
  Writer(List(s"$x + $y"), x + y)

val w = for {
  a <- addW(1, 2)
  b <- addW(3, 4)
  c <- addW(a, b)
} yield c

现在,我们可以运行计算:

scala> val (log, res) = w.run
log: List[String] = List(1 + 2, 3 + 4, 3 + 7)
res: Int = 10

我们可以用同样的方法处理State
def addS(x: Int, y: Int) =
  State((log: List[String]) => (log |+| List(s"$x + $y"), x + y))

val s = for {
  a <- addS(1, 2)
  b <- addS(3, 4)
  c <- addS(a, b)
} yield c

接着:

scala> val (log, res) = s.run(Nil)
log: List[String] = List(1 + 2, 3 + 4, 3 + 7)
res: Int = 10

然而,这种方法略显冗长,我们也可以使用State做许多其他的事情,这是Writer所不能做到的。

因此,故事的寓意是,只要可能,应该使用Writer。你的解决方案将更加清晰、简洁,并且你会得到使用适当抽象的满足感。

然而,很多时候Writer并不能提供你所需要的全部功能,在这些情况下,State将会在那里等待你。


1
tl;dr 状态是可读可写的,而作者只能写。
使用状态,您可以访问先前存储的数据,并在当前计算中使用此数据:
def myComputation(x: A) =
  State((myState: List[A]) => {
        val newValue = calculateNewValueBasedOnState(x,myState)
        (log |+| List(newValue), newValue)
  })

使用Writer,您可以将数据存储在某些您无法访问的对象中,您只能向该对象写入数据。

另一个答案似乎暗示了Writer允许在最后一次读取累积值:“它不允许您读取累积状态(直到您在最后结算)”。累积一些值但永远无法读取它,这有什么意义呢? - Răzvan Flavius Panda
@RăzvanFlaviusPanda 我认为记录器是编写者的标准示例,这种情况下,您希望将内容写入文件或第三方系统,并且程序的其余部分不应关心或访问日志。 - javier

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接