什么是适当的monad或序列理解来映射和携带状态?

Mik*_*rle 14 monads state scala state-monad

我正在编写一个编程语言解释器.

我需要正确的代码习惯用法来评估表达式序列以获取其值的序列,并在评估发生时将状态从一个评估器传播到下一个评估器.我想要一个函数式编程习惯用法.

它不是折叠因为结果像地图一样出现.这不是地图,因为国家支持.

我所拥有的是这个代码,我用来试图解决这个问题.首先考虑几行试验台:

// test rig
class MonadLearning extends JUnit3Suite {

  val d = List("1", "2", "3") // some expressions to evaluate. 

  type ResType = Int 
  case class State(i : ResType) // trivial state for experiment purposes
  val initialState = State(0)

// my stub/dummy "eval" function...obviously the real one will be...real.
  def computeResultAndNewState(s : String, st : State) : (ResType, State) = {
    val State(i) = st
    val res = s.toInt + i
    val newStateInt = i + 1
    (res, State(newStateInt))
  }
Run Code Online (Sandbox Code Playgroud)

我目前的解决方案 使用在评估地图主体时更新的var:

  def testTheVarWay() {
    var state = initialState
    val r = d.map {
      s =>
        {
          val (result, newState) = computeResultAndNewState(s, state)
          state = newState
          result
        }
    }
    println(r)
    println(state)
  }
Run Code Online (Sandbox Code Playgroud)

我有我认为不可接受的解决方案使用foldLeft,它做我称之为"折叠时包你好"的成语:

def testTheFoldWay() {

// This startFold thing, requires explicit type. That alone makes it muddy.
val startFold : (List[ResType], State) = (Nil, initialState)
val (r, state) = d.foldLeft(startFold) {
  case ((tail, st), s) => {
    val (r, ns) = computeResultAndNewState(s, st)
    (tail :+ r, ns) // we want a constant-time append here, not O(N). Or could Cons on front and reverse later
  }
}

println(r)
println(state)

}
Run Code Online (Sandbox Code Playgroud)

我也有一些递归变量(很明显,但也不清楚或者很有动力),一个使用几乎可以容忍的流:

def testTheStreamsWay() {
  lazy val states = initialState #:: resultStates // there are states
  lazy val args = d.toStream // there are arguments
  lazy val argPairs = args zip states // put them together
  lazy val resPairs : Stream[(ResType, State)] = argPairs.map{ case (d1, s1) => computeResultAndNewState(d1, s1) } // map across them
  lazy val (results , resultStates) = myUnzip(resPairs)// Note .unzip causes infinite loop. Had to write my own.

  lazy val r = results.toList
  lazy val finalState = resultStates.last

  println(r)
  println(finalState)
}
Run Code Online (Sandbox Code Playgroud)

但是,我想不出任何东西作为紧凑型或明确为原来的"变种"上面的解决方案,我愿意住在一起,但我认为人谁吃/饮料/睡单子成语是怎么回事,只是说..用这个...(希望!)

Tra*_*own 19

使用map-with-accumulator组合器(简单方法)

你想要的高阶函数是mapAccumL.它位于Haskell的标准库中,但对于Scala,您必须使用类似Scalaz的东西.

首先是导入(注意我在这里使用Scalaz 7;对于你导入的先前版本Scalaz._):

import scalaz._, syntax.std.list._
Run Code Online (Sandbox Code Playgroud)

然后它是一个单行:

scala> d.mapAccumLeft(initialState, computeResultAndNewState)
res1: (State, List[ResType]) = (State(3),List(1, 3, 5))
Run Code Online (Sandbox Code Playgroud)

请注意,我必须颠倒评估者的参数和返回值元组的顺序以匹配期望的签名mapAccumLeft(在两种情况下都是状态优先).

随着州monad(稍微不那么简单的方式)

正如PetrPudlák在另一个答案中指出的那样,你也可以使用状态monad来解决这个问题.Scalaz实际上提供了许多设施,使得与州monad合作比他的答案中的版本更容易,并且它们不适合评论,所以我在这里添加它们.

首先,Scalaz确实提供了一个mapM刚刚调用的东西traverse(正如PetrPudlák在他的评论中指出的那样,这是一个更普遍的).所以假设我们有以下内容(我在这里再次使用Scalaz 7):

import scalaz._, Scalaz._

type ResType = Int
case class Container(i: ResType)

val initial = Container(0)
val d = List("1", "2", "3")

def compute(s: String): State[Container, ResType] = State {
  case Container(i) => (Container(i + 1), s.toInt + i)
}
Run Code Online (Sandbox Code Playgroud)

我们可以这样写:

d.traverse[({type L[X] = State[Container, X]})#L, ResType](compute).run(initial)
Run Code Online (Sandbox Code Playgroud)

如果你不喜欢丑陋的lambda,你可以像这样摆脱它:

type ContainerState[X] = State[Container, X]

d.traverse[ContainerState, ResType](compute).run(initial)
Run Code Online (Sandbox Code Playgroud)

但它变得更好!Scalaz 7为您提供了一个traverse专门用于状态monad的版本:

scala> d.traverseS(compute).run(initial)
res2: (Container, List[ResType]) = (Container(3),List(1, 3, 5))
Run Code Online (Sandbox Code Playgroud)

而且好像这还不够,甚至还有run内置的版本:

scala> d.runTraverseS(initial)(compute)
res3: (Container, List[ResType]) = (Container(3),List(1, 3, 5))
Run Code Online (Sandbox Code Playgroud)

mapAccumLeft在我看来,仍然没有版本那么好,但很干净.

  • 我希望我可以给你一个以上的upvote来提出`traverse`和`traverseS` ...我不得不在其他人的代码中遇到haskell的`mapM`,以便意识到我正在寻找它. (3认同)
  • 当然我不介意,你的回答更好.我在那里看到了状态monad,但我不知道如何在Scala中正确使用它.实际上,在Haskell中它也被称为[traverse](http://hackage.haskell.org/packages/archive/base/latest/doc/html/Data-Traversable.html#v:traverse).不同之处在于`mapM`仅适用于列表(并且更易于理解),而`traverse`则适用于任何[traversable](http://hackage.haskell.org/packages/archive/base/latest/doc/html/ Data-Traversable.html)结构.(我使用的是Scalaz 6,这只是Scalaz 7吗?) (2认同)