Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala: How to define a function whose input is (f, args) and whose output is f(args)?

How can you define a function myEval(f, args) in Scala which takes as input another function f and arguments args and whose output is f(args)?

I don't want myEval to have any prior knowledge about the arity or argument types of f.

Why is this useful? It is one way to solve the problem of implementing a generic timeMyFunction(f, args) method. If there's a way to do this by some sort of lazy val construction, that would also be interesting.

Edit: The better way to implement a timing method is explained in this question. By calling timeMyFunction( { f(args) } ), the function call is wrapped in an anonymous function Unit => Unit. So timeMyFunction only needs to take 0-arity functions.

Edit 2: See Dirk's answer for what is perhaps a more efficient way which avoids an anonymous function by passing f by reference.

So my justification for the question is now purely my Scala education.

like image 426
expz Avatar asked Apr 15 '14 09:04

expz


2 Answers

The Scala standard library isn't going to help you generalize over arity in most cases, but Shapeless is perfect for this. Here's how you could write your function in Shapeless 1.2.4:

import shapeless._

def foo[F, P <: Product, A <: HList, R](f: F, p: P)(implicit
  fl: FnHListerAux[F, A => R],
  pl: HListerAux[P, A]
): R = fl(f)(pl(p))

And then:

scala> foo((i: Int, s: String) => s * i, (3, "a"))
res0: String = aaa

It looks complicated, but essentially you're just saying that you need evidence that a function f of some arbitrary arity can be converted to a single-argument function from a heterogeneous list A to the result R, and that the tuple P can be converted to a heterogeneous list of the same type.

like image 148
Travis Brown Avatar answered Oct 23 '22 03:10

Travis Brown


An alternative using pass-by-name would be:

def timeIt[T](thunk: =>T): T = {
    // ... set-up the timer 
    val answer: T = thunk
    // ... evaluate the result of the timer
    answer  // in case, you need the result and want the timing to
}           // happen just as a side-effect

timeIt(someFunction(someArg1, ...))

Though this looks as if it would call someFunction directly, it does not, since timeIt takes the argument "by name", i.e., the scala compiler generates a hidden closure, which performs the call, when the actual value is needed in timeIt itself.

This variant may introduce some timing noise due to the overhead of the "pass-by-name" convention.

like image 30
Dirk Avatar answered Oct 23 '22 03:10

Dirk