Search code examples
scalapattern-matchingenvironmentinterpretersemantics

Updating an Environment in Scala


I'm taking some predefined semantic rules and implementing them as an interpreter for the lettuce language using Scala. In Multi-Let, I'm trying to update an environment variable using two lists. I'm sort of new to Scala so I'm not too sure how to do this without converting the environment variable to a List. Is there a way to manipulate the return type in my zip function? I'm getting the following error message. My goal is to get a single updated map rather than a list of updated maps.

cmd2.sc:44: type mismatch;
 found   : List[scala.collection.immutable.Map[String,Helper.this.Value]]
 required: Helper.this.Environment
    (which expands to)  scala.collection.immutable.Map[String,Helper.this.Value]
            evalExpr(e2,newEnv)
                        ^Compilation Failed
sealed trait Expr
case class Const(d: Double) extends Expr
case class Ident(s: String) extends Expr
case class Plus(e1: Expr, e2: Expr) extends Expr
case class Mult(e1: Expr, e2: Expr) extends Expr 
case class Let(id: String, e1: Expr, e2: Expr) extends Expr
case class MultiLet(id: List[String], eList: List[Expr], e2: Expr) extends Expr

sealed trait Value
case class NumValue(f: Double) extends Value
case object Error extends Value /* -- Do not return Error -- simply throw an new IllegalArgumentException whenever you encounter an erroneous case --*/

type Environment = Map[String, Value]

def evalExpr(e: Expr, env: Environment): Value = {
    
    e match {
       
        case Let(x, e1, e2) => {
            val v1 = evalExpr(e1, env) 
            val newEnv = env.updated(x,v1);
            evalExpr(e2,newEnv)
        }

        case MultiLet(xList, eList, e2) => {
            val vList = eList.map(evalExpr(_, env))
            val newEnv = (xList, vList).zipped.map{ (x, v) => env.updated(x,v)}
            println(newEnv)
            evalExpr(e2,newEnv)
        }
    }
}

Solution

  • I assume that we are dealing the case of Const the following way:

    case Const(d) => NumValue(d)
    

    In order to get an updated environment, you need to use foldLedt:

    val newEnv = (xList, vList).zipped.foldLeft(env) { (e, kv) =>
      e.updated(kv._1, kv._2)
    }
    

    Let's now test it:

    When starting the program, the environment is empty, so we are running with an empty map:

    evalExpr(MultiLet(List("1", "2"), List(Const(4), Const(5)), Const(6)), Map.empty)
    

    The output is:

    Map(1 -> NumValue(4.0), 2 -> NumValue(5.0))
    

    Then, we get to a function where do not yet have conflicts:

    evalExpr(MultiLet(List("2"), List(Const(5)), Const(6)), Map("1" -> NumValue(7)))
    

    The output is:

    Map(1 -> NumValue(4.0), 2 -> NumValue(7.0))
    

    And the last case is when we have conflicting variables:

    evalExpr(MultiLet(List("1", "2"), List(Const(4), Const(5)), Const(6)), Map("1" -> NumValue(7)))`
    

    Which outputs:

    Map(1 -> NumValue(4.0), 2 -> NumValue(5.0))
    

    Code snippet can be found at scastie.