13

Having a background in Haskell I am currently trying to get familiar with Scala.

I encountered some problems trying to translate a small, extensible expression language from Haskell into Scala. The underlying issue of writing a data type that is extensible with both new data-variants and operations is commonly known as the expression problem.

My original solution in Haskell uses type classes and instance declarations with constraints. The base of my expression is defined as follows:

module Expr where

class Expr e where
 eval :: e -> Integer

data Lit = Lit Integer
instance Expr Lit where
  eval (Lit l) = l

data Plus a b = (Expr a, Expr b) => Plus a b
instance (Expr a, Expr b) => Expr (Plus a b) where
  eval (Plus x y) = (eval x) + (eval y)

Then, I have one data-extension that adds multiplication:

module ExprWithMul where
import Expr

data Mul a b = (Expr a, Expr b) =>  Mul a b
instance (Expr a, Expr b) => Expr (Mul a b) where
  eval (Mul x y) = (eval x) * (eval y)

Let's take a pretty-printer as an operational extension:

module FormatExpr where
import Expr

class (Expr t) => FormatExpr t where
  format :: t -> String

instance FormatExpr Lit where
  format (Lit l) = show l

instance (FormatExpr a, FormatExpr b) => FormatExpr (Plus a b) where
  format (Plus x y) = "(" ++ (format x) ++ "+" ++ (format y) ++ ")"

And, finally, in the fourth module the two independent extensions can be combined:

module FormatExprWithMult where
import FormatExpr
import ExprWithMul

instance (FormatExpr a, FormatExpr b) => FormatExpr (Mul a b) where
  format (Mul x y) = "(" ++ (format x) ++ "*" ++ (format y) ++ ")"

Now for my problem: Usually type classes from haskell are translated to the concept-pattern with implicits in Scala. This is how far I got:

abstract class Expr[A] { // this corresponds to a type class
  def eval(e:A): Int;
}

case class Lit(v: Int)
implicit object ExprLit extends Expr[Lit] {
 def eval(e: Lit) = x.v;
}
case class Plus[A,B] (e1: A, e2: B) (implicit c1: Expr[A], c2: Expr[B])

Here I am stuck with implementing the implicit object for Plus. How do I declare an implicit object with type parameters and constraints?

I know that there are other solution for the expression problem in Scala, I am however interested in this version in particular.

Thank you all for reading my somewhat lengthy question.

Don Stewart
  • 137,316
  • 36
  • 365
  • 468
FloDo
  • 215
  • 1
  • 9
  • Here's a similar question with several answers: http://stackoverflow.com/questions/5408861/what-are-type-classes-in-scala-useful-for – Jonathan Warden Jul 19 '13 at 22:30

2 Answers2

15

First attempt (flawed):

case class Plus[A,B] (e1: A, e2: B) (implicit c1: Expr[A], c2: Expr[B]) {
    implicit object ExprPlus extends Expr[Plus[A, B]] { 
        def eval(p:Plus[A, B]) = c1.eval(p.e1) + c2.eval(p.e2)
    }
}

Edit 1:

The above isn't sufficiently powerful (you can't add two Plus expressions), and the implicit witness need not be defined inside of the Plus case class... try this instead:

case class Plus[A,B] (e1: A, e2: B) (implicit val c1: Expr[A], c2: Expr[B])
implicit def ExprPlus[A, B](implicit c1: Expr[A], c2: Expr[B]) = 
    new Expr[Plus[A, B]] { 
        def eval(p:Plus[A, B]) = c1.eval(p.e1) + c2.eval(p.e2)
    }

Edit 2:

Here's a (perhaps) slightly more idiomatic version:

case class Plus[A: Expr, B: Expr] (e1: A, e2: B)
implicit def ExprPlus[A: Expr, B: Expr] = new Expr[Plus[A, B]] {
    def eval(p:Plus[A, B]) = implicitly[Expr[A]].eval(p.e1) + 
                             implicitly[Expr[B]].eval(p.e2)
}
Tom Crockett
  • 30,818
  • 8
  • 72
  • 90
  • Oh, this *seems* kind of obvious. Thank you for the answer, but his only partially solves my problem. When you can't define the implicit conversion outside of the definition of "class Plus", how can you do further operational extensions? Am I missing something here? – FloDo Sep 08 '10 at 21:51
  • 1
    Implicit conversions and values may be defined in objects and in package objects and these have a global / static characteristic that allows them to be imported into any scope. I do not know what you mean by "further operational extensions," however. – Randall Schulz Sep 08 '10 at 21:56
  • By operational extensions I refer to adding, for example, pretty-printing functionality to expressions, without modifying existing code. Like in the Haskell code I posted. Wouldn't this require a way to add implicit objects *outside* of the class definition? – FloDo Sep 08 '10 at 22:01
  • Now this is what I was looking for. Thank you, I was not considering using a implicit function definition. Though it solves the same problem, the solution in Haskell looks somewhat cleaner, but I guess I have to live with that. – FloDo Sep 08 '10 at 22:16
  • @FloDo: It sounds like you're looking for solutions to the Expression Problem in Scala. There has been some work done on this front. I suggest searching with appropriate terms on Google Scholar. – Randall Schulz Sep 08 '10 at 22:20
  • 2
    @FloDo: See my second edit for a marginally cleaner-looking solution... I think there is work underway to add more syntax sugar for type classes to Scala, but it will never be as pretty as Haskell ;) – Tom Crockett Sep 08 '10 at 22:23
  • @Randall Schulz: he mentioned the expression problem in his question :) – Tom Crockett Sep 08 '10 at 22:24
  • 1
    It would be really nice if `implicitly` was applied, well... implicitly – Tom Crockett Sep 08 '10 at 22:36
  • It should be said that the initial solution, while admirably compact, is not particularly idiomatic Scala. Implicit conversions are almost always defined outside of the object being converted. It's also weaker than the second form, as it makes it more difficult to provide alternate conversions (e.g. the addition and multiplication Monoids over Ints, or simply alternate definitions of a PrettyPrinting instance) – Dave Griffith Sep 08 '10 at 23:28
  • @Dave Griffith: I agree, I only left it there to avoid "breaking" comments that referred to it – Tom Crockett Sep 09 '10 at 00:11
  • 1
    Do note that this is fully strict. Case classes do not support lazy (by-name) arguments. – Apocalisp Sep 09 '10 at 09:50
  • I think the best place to put this is inside `Plus` object companion, though, of course, that doesn't need to be the case. – Daniel C. Sobral Sep 09 '10 at 16:47
6

Here is the complete implementation of Expression Problem in scala using Type classes

  trait Exp
  case class Lit(value: Int) extends Exp
  case class Add[A <: Exp, B <: Exp](left: A, right: B) extends Exp

Eval type class and implicit implementations

  //type class
  trait Eval[E] {
    def eval(e: E): Int
  }

  implicit def litEval = new Eval[Lit] {
    def eval(l: Lit) = l.value
  }

  implicit def addEval[A <: Exp, B <: Exp](implicit e1: Eval[A], e2: Eval[B]) = new Eval[Add[A, B]] {
    def eval(a: Add[A, B]) = e1.eval(a.left) + e2.eval(a.right)
  }

Lets extend the solution by adding new Type called Mult

case class Mult[A <: Exp, B <: Exp](left: A, right: B) extends Exp

implicit def mulEval[A <: Exp, B <: Exp](implicit e1: Eval[A], e2: Eval[B]) = new Eval[Mult[A, B]] {
    def eval(m : Mult[A, B]) = e1.eval(m.left) * e2.eval(m.right)
}

Now the expressions can be evaluated like this

def expressionEvaluator[A <: Exp](exp: A)(implicit e : Eval[A]) = {
    e.eval(exp)
}

def main(args: Array[String]): Unit = {
   // (3 + 4) * 7
   val exp1 = Mult(Add(Lit(3), Lit(4)), Lit(7))
   println("" + expressionEvaluator(exp1))
}

Lets extend the system by adding a new Operation Print

  //type class
  trait Print[P] {
    def print(p: P): Unit
  }

  implicit def litPrint = new Print[Lit] {
    def print(l: Lit) = Console.print(l.value)
  }

  implicit def addPrint[A <: Exp, B <: Exp](implicit p1: Print[A], p2 : Print[B]) = new Print[Add[A, B]] {
    def print(a : Add[A, B]) = { p1.print(a.left); Console.print(" + "); p2.print(a.right); }
  }

  implicit def mulPrint[A <: Exp, B <: Exp](implicit p1: Print[A], p2: Print[B]) = new Print[Mult[A, B]] {
    def print(m : Mult[A, B]) = { p1.print(m.left); Console.print(" * "); p2.print(m.right) }
  }

Define a new method to print the Expressions

def printExpressions[A <: Exp](exp : A)(implicit p : Print[A]) = {
    p.print(exp)
}

Update the main method to print the expression

def main(args: Array[String]): Unit = {
    val exp1 = Mult(Add(Lit(3), Lit(4)), Lit(7))

    print("Expression : ")
    printExpressions(exp1)
    print(", Evaluated to : " + expressionEvaluator(exp1))
}

Entire solution can be executed by wrapping the code inside an object.

Prashant Kalkar
  • 602
  • 1
  • 7
  • 19