scala – 什么是适当的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)) } 我目前的解决方案使用更新的var,因为地图的正文被评估: def testTheVarWay() { var state = initialState val r = d.map { s => { val (result,newState) = computeResultAndNewState(s,state) state = newState result } } println(r) println(state) } 我有我认为使用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) } 我也有几个递归的变化(这是显而易见的,但也不清楚或有很好的动机),一个使用几乎可以容忍的流: 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) } 但是,我无法确定任何像上面原来的“var”解决方案那样紧凑或清晰的东西,我愿意和他一起生活,但是我认为吃/喝/睡眠monad成语的人会说..使用这个…(希望!) 解决方法
使用映射与累加器组合器(简单的方法)
你想要的高阶函数是mapAccumL.这是在Haskell的standard library,但是对于Scala,你将不得不使用像Scalaz这样的东西. 首先进口(请注意,我在这里使用Scalaz 7;以前版本您将导入Scalaz._): import scalaz._,syntax.std.list._ 然后它是一个单行: scala> d.mapAccumLeft(initialState,computeResultAndNewState) res1: (State,List[ResType]) = (State(3),List(1,3,5)) 请注意,我不得不扭转评估者参数的顺序和返回值元组,以匹配mapAccumLeft所预期的签名(在这两种情况下都是第一位). 与国家monad(稍微不那么简单的方法) 正如彼得·普拉达(PetrPudlák)在另一个答案中指出的那样,你也可以使用状态monad来解决这个问题. Scalaz实际上提供了许多与国家monad一起使用的设施,比他的回答建议的版本要容易得多,而且不符合评论的要求,所以我把它们添加到这里. 首先,Scalaz确实提供了一个mapM,它只是被称为遍历(这在PetrPudlák在他的评论中注意到一点).所以假设我们有以下(我在这里再次使用Scalaz 7): import scalaz._,Scalaz._ type ResType = Int case class Container(i: ResType) val initial = Container(0) val d = List("1","3") def compute(s: String): State[Container,ResType] = State { case Container(i) => (Container(i + 1),s.toInt + i) } 我们可以写这个: d.traverse[({type L[X] = State[Container,X]})#L,ResType](compute).run(initial) 如果你不喜欢这个丑陋的lambda,你可以像这样去掉它: type ContainerState[X] = State[Container,X] d.traverse[ContainerState,ResType](compute).run(initial) 但它变得更好! Scalaz 7给你一个专为国家monad的横越版本: scala> d.traverseS(compute).run(initial) res2: (Container,List[ResType]) = (Container(3),5)) 而如果这还不够,还有一个版本的内置游戏: scala> d.runTraverseS(initial)(compute) res3: (Container,5)) 仍然不如mapAccumLeft版本好,在我看来,但很干净. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |