我正在写一个编程语言解释器。
我需要有正确的代码风格,以两者的计算表达式的序列来获得它们的值的序列,并从一个评估传播状态下一下一作为评价发生。 我想一个函数式编程成语这一点。
这不是因为褶皱的结果出来就像一张地图。 这不是一张地图,因为国家的托对面。
我有什么是这个代码我使用揣摩这哪。 与试验台的几行第一熊:
// 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)
}
但是,我想不出任何东西作为紧凑型或明确为原来的“变种”上面的解决方案,我愿意住在一起,但我认为人谁吃/饮料/睡单子成语是怎么回事,只是说.. 。使用这个......(希望!)
Answer 1:
随着地图与累加器的组合子(简单的方法)
你想要的高阶函数是mapAccumL
。 这是在Haskell的标准库 ,但斯卡拉你必须使用像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
(在这两种情况下,第一状态)。
随着国家单子(略少简单的方法)
由于切赫Pudlák的另一种回答指出,你也可以使用状态单子来解决这个问题。 Scalaz实际上提供了许多,使符合国家单子比版本更容易在工作他的回答暗示设施,他们将不适合评论,所以我在这里加入他们。
首先,Scalaz确实提供了mapM
-它只是被称为traverse
(这是一个小更普遍,因为切赫Pudlá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)
}
我们可以这样写:
d.traverse[({type L[X] = State[Container, X]})#L, ResType](compute).run(initial)
如果你不喜欢丑类型的λ,你可以摆脱这样的:
type ContainerState[X] = State[Container, X]
d.traverse[ContainerState, ResType](compute).run(initial)
但它会变得更好! Scalaz 7为您提供了一个版本的traverse
这是专门为国家单子:
scala> d.traverseS(compute).run(initial)
res2: (Container, List[ResType]) = (Container(3),List(1, 3, 5))
而如果这还不够,还有的甚至用一个版本run
内置的:
scala> d.runTraverseS(initial)(compute)
res3: (Container, List[ResType]) = (Container(3),List(1, 3, 5))
还没像你一样的mapAccumLeft
版本,在我看来,但还算干净。
Answer 2:
什么你所描述的是状态单子内的计算。 我认为,回答你的问题
这不是因为褶皱的结果出来就像一张地图。 这不是一张地图,因为国家的托对面。
是它的使用状态单子一个单子地图 。
状态单子的值是读了一些内部状态计算,可能修改它,并返回一定的价值。 它通常在Haskell使用(参见这里或点击这里 )。
对于Scala中,有一个trait
在ScalaZ库调用国家是它的模型(见源 )。 有在实用方法States
创建的实例的State
。 需要注意的是从视图中的一元点State
只是一个一元的价值 。 最初,这可能看起来令人困惑,因为它是由取决于状态的函数来描述。 (A一元函数将是类型的东西A => State[B]
接下来,你需要的是一个一元映射函数的计算表达式的值,通过计算线程的状态。 在Haskell中,有一个库方法MAPM是做到了这一点,当专门的状态单子。
在Scala中,有没有这样的库函数(如果是,请指正)。 但它可以创建一个。 举一个完整的例子:
import scalaz._;
object StateExample
extends App
with States /* utility methods */
{
// The context that is threaded through the state.
// In our case, it just maps variables to integer values.
class Context(val map: Map[String,Int]);
// An example that returns the requested variable's value
// and increases it's value in the context.
def eval(expression: String): State[Context,Int] =
state((ctx: Context) => {
val v = ctx.map.get(expression).getOrElse(0);
(new Context(ctx.map + ((expression, v + 1)) ), v);
});
// Specialization of Haskell's mapM to our State monad.
def mapState[S,A,B](f: A => State[S,B])(xs: Seq[A]): State[S,Seq[B]] =
state((initState: S) => {
var s = initState;
// process the sequence, threading the state
// through the computation
val ys = for(x <- xs) yield { val r = f(x)(s); s = r._1; r._2 };
// return the final state and the output result
(s, ys);
});
// Example: Try to evaluate some variables, starting from an empty context.
val expressions = Seq("x", "y", "y", "x", "z", "x");
print( mapState(eval)(expressions) ! new Context(Map[String,Int]()) );
}
这样,您就可以创建一个采取一些简单的参数功能和返回State
,然后用它们组合成更复杂的State.map
或State.flatMap
(或者也许是更好使用for
内涵),然后就可以运行在整个计算通过表达式的列表mapM
。
又见http://blog.tmorris.net/posts/the-state-monad-for-scala-users/
编辑:见特拉维斯布朗的回答,他描述了如何更很好地使用状态单子Scala中。
他还要求:
但是为什么,当有不正是你在这种情况下,需要一个标准组合子? (我问这是人谁是被打了使用状态单子时mapAccumL会怎么做。)
这是因为原来的问题问:
这不是因为褶皱的结果出来就像一张地图。 这不是一张地图,因为国家的托对面。
我相信正确的答案是它是使用状态单子一个单子地图。
使用mapAccumL
肯定是更快,均不内存和CPU开销。 但国家单子捕获正在发生的事情, 问题的本质的概念。 我相信,在许多(如果不是大多数)情况下,这是更重要的。 一旦我们认识到这个问题的本质,我们既可以使用高层次的概念很好地描述了解决方案(也许牺牲速度/记忆一点点)或优化它要快(或者甚至能做到两者)。
在另一方面, mapAccumL
解决这个特定的问题,但并没有给我们一个更广泛的答案。 如果我们需要修改它一点,它可能会发生,它不会工作了。 或者,如果库开始很复杂,代码就可以开始是凌乱的,我们不知道如何改进它,如何让原来的想法清晰一次。
例如,在评估状态表达式的情况下,该库可能会变得复杂和复杂。 但是,如果我们使用状态单子,我们可以建立图书馆周围的小功能,每次服用一些参数和返回类似State[Context,Result]
。 这些原子计算可以结合使用更复杂的flatMap
方法或for
内涵,最后我们将构建所需的任务。 其原理将保持在整个库相同,最后的任务也将是东西,返回State[Context,Result]
。
要总结:我不使用状态单子说是最好的解决办法,当然这不是最快的国家之一。 我相信这是最说教的功能程序员 - 它描述了在一个干净的,抽象的方式问题。
Answer 3:
你可以递归地做到这一点:
def testTheRecWay(xs: Seq[String]) = {
def innerTestTheRecWay(xs: Seq[String], priorState: State = initialState, result: Vector[ResType] = Vector()): Seq[ResType] = {
xs match {
case Nil => result
case x :: tail =>
val (res, newState) = computeResultAndNewState(x, priorState)
innerTestTheRecWay(tail, newState, result :+ res)
}
}
innerTestTheRecWay(xs)
}
递归函数式编程的普遍做法,是大部分的时间更容易阅读,书写和理解比循环。 事实上Scala没有比其他任何循环while
。 fold
, map
, flatMap
, for
(这只是糖flatMap /地图)等都是递归的。
这种方法是尾递归的,将被编译器不能建立一个堆栈进行优化,所以它是绝对安全使用。 您可以添加@ annotation.tailrec annotaion,强制编译应用尾递归消除。 如果你的方法不tailrec编译器会又抱怨。
编辑:更名内方法,以避免模糊
文章来源: what is proper monad or sequence comprehension to both map and carry state across?