===== Lecture 09. Polymorphic expressions ====== trait Expr[A] { // reminiscent of factory methods def +(other: Expr[A]): Expr[A] = Plus(this, other) def *(other: Expr[A]): Expr[A] = Mult(this, other) } case class Plus[A](left: Expr[A], right: Expr[A]) extends Expr[A] case class Mult[A](left: Expr[A], right: Expr[A]) extends Expr[A] case class Atom[A](v: A) extends Expr[A] case class Var[A](s: String) extends Expr[A] /* we need to implement a new type objects, which tells us how we should interpret + and *, depending on who A is. */ case class Strategy[A](atom:A => A, plus: (A,A) => A, mult: (A,A) => A) object Today extends App{ /* More polymorphism * * a little piece of code which might be related to interpreters or * compilers. * * We would like to define expressions such as: * Plus(1,Mult(2,3)) vs x + (2 * 3) * and we would like to evaluate such expressions * input: "1 + (2 * 3)", we would like to skip parsing * e.leftSide * e.rightSide * */ /* 1 + 2 * 3 + / \ 1 * / \ 2 3 * / \ + 3 / \ 1 2 */ val e = Plus(Atom(1), Mult(Atom(2), Atom(3))) type Store[A] = Map[String, A] def eval[A](store: Store[A])(str: Strategy[A])(e: Expr[A]): Option[A] = { def aux(e: Expr[A]): Option[A] = e match { case Plus(e1, e2) => (aux(e1), aux(e2)) match { case (Some(x), Some(y)) => Some(str.plus(x, y)) case _ => None } case Mult(e1, e2) => (aux(e1), aux(e2)) match { case (Some(x), Some(y)) => Some(str.mult(x, y)) case _ => None } case Atom(v) => Some(str.atom(v)) case Var(s) => store.get(s) // something important happens here! (error handling) // What do we do, if the store does not contain s? // We do not know -> we need to "pass on" an error } aux(e) } /* Without modifying functionality, what could we improve (in terms of usability) with this code? 1. We can improve the legibility of expression constructors. We want to define "+" and "*" and use them INFIX e1 + e2 translates to e1.+(e2) */ val e1 = Var("x") + Atom(2) * Atom(3) //1 + 2 * 3 // Scala integer expression /* 2. What would be a natural extension for expressions? What are expressions missing? (Div is an option) We would like to include variables! 3. If we include variables, we need to include error-handling. Done 4. What if atoms could be ANYTHING? - now, atoms are integers - atoms could be: booleans, lists, functions * */ val strInt = Strategy[Int](x => x, _ + _, _ * _) val strBool = Strategy[Boolean](x => x, _ || _, _ && _) println(eval(Map("x" -> 1))(strInt)(e1)) //val strList = }