I have method eval which takes List of Function and arguments, currently I am writing case for every possible function. How can I write it more generic?
implicit class Expression(p: Product) {
def eval = p.productIterator.toList match {
case (f: ((Any) => Any)) :: p1 :: Nil => f(p1)
case (f: ((Any, Any) => Any)) :: p1 :: p2 :: Nil => f(p1, p2)
case (f: ((Any, Any, Any) => Any)) :: p1 :: p2 :: p3 :: Nil => f(p1, p2, p3)
}
}
def print = (x: Any) => println(">> " + x)
def add = (x: Any, y: Any) => x.asInstanceOf[Int] + y.asInstanceOf[Int]
(print, "test").eval
(add, 2, 3).eval
You could write something like this using shapeless, which has the benefit that it is type safe and checked at compile time (compared with using Any
in your Expression.eval
).
import shapeless._
import ops.hlist._
import syntax.std.function._
import ops.function._
def eval[P <: Product, G <: HList, F, L <: HList, R](
p: P
)(implicit
gen: Generic.Aux[P, G],
ihc: IsHCons.Aux[G, F, L],
ftp: FnToProduct.Aux[F, L => R]
) = {
val l = gen.to(p)
val (func, args) = (l.head, l.tail)
func.toProduct(args)
}
Which you could use as :
def add(a: Int, b: Int) = a + b
val stringLength: String => Int = _.length
eval((add _, 1, 2)) // Int = 3
eval((stringLength, "foobar")) // Int = 6
eval((stringLength, 4)) // does not compile
You can also use it with a case class if it follows the same format (first a function then the right arguments for that function) :
case class Operation(f: (Int, Int) => Int, a: Int, b: Int)
eval(Operation(_ + _, 1, 2)) // Int = 3
eval(Operation(_ * _, 1, 2)) // Int = 2