co to jest właściwa monada lub rozumienie sekwencji, aby mapować i przenosić stan w poprzek?

Piszę interpreter języka programowania.

Potrzebuję odpowiedniego idiomu kodu, aby zarówno ocenić sekwencję wyrażeń, aby uzyskać sekwencję ich wartości, jak i propagować stan od jednego ewaluatora do następnego obok, gdy ewaluacje mają miejsce. Chciałbym do tego funkcjonalny idiom programowania.

To nie jest fałd, ponieważ wyniki wyglądają jak mapa. To nie jest mapa z powodu podpory stanu.

Mam ten kod, którego używam, aby spróbować to rozgryźć. Najpierw zrób kilka linii stanowiska testowego:

// 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))
  }

Moje obecne rozwiązanie. Używa var, który jest aktualizowany, gdy ciało mapy jest oceniane:

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

Używam foldLeft, który uważam za rozwiązania, które uważam za nieakceptowalne.

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)

}

Mam też kilka wariacji rekurencyjnych (które są oczywiste, ale też nie są jasne lub dobrze zmotywowane), jedną wykorzystującą strumienie, które są prawie tolerowane:

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)
}

Ale nie mogę wymyślić niczego tak zwięzłego ani jasnego, jak oryginalne rozwiązanie „var”, z którym chciałbym żyć, ale myślę, że ktoś, kto je / pije / śpi w monotonnych idiomach, po prostu powie ... . użyj tego ... (Mam nadzieję!)

questionAnswers(3)

yourAnswerToTheQuestion