5

I have a function def f(a: Int, b: Int, c: Int, d: Int, ...) and I'd like to supply a list of consecutive integers as parameters (in a unit test).

Is there a neat way to supply (1 to N).toList to f? Since the function is not def f(x: Int*) I cannot simply use list: _* with list the list of integers.

Max Power
  • 952
  • 9
  • 24
  • Writing as a comment since it's not a full-blown answer, but might take you on the right path: you can curry your function to make it `Int => Int => Int => Int => Int`, and then take your list of integers and keep (partially) applying them one by one to the function via `foldLeft`. This question goes into details of that: https://stackoverflow.com/questions/7606587/applying-an-argument-list-to-curried-function-using-foldleft-in-scala If you had a tuple instead of a list, you could apply it to `f.tupled`, but if you do have a list then forget that. – slouc Jan 24 '19 at 09:25

2 Answers2

2

I don't think you can do it in standard Scala in a typesafe way, but it's possible to do it using the Shapeless library. Even if you don't use this library in the project, you can depend on it only for the test scope.

Here is the code sample:

import shapeless._
import shapeless.ops.function._
import shapeless.ops.traversable._

def applyToList[F, HL <: HList, R](f: F)(args: List[Any])(implicit
  // convert `(T1, T2, ...) => R` to a function from a single HList argument `HL => R`
  fnToProduct: FnToProduct.Aux[F, HL => R],  
  // convert the argument list to the HList, expected by the converted function
  toHList: FromTraversable[HL]
): R = {
  toHList(args) match {
    case Some(hargs) => fnToProduct(f)(hargs)
    case None => sys.error("argument list not applicable to function")
  }
}

Using it is as simple as this:

def f(a: Int, b: Int, c: Int, d: Int): Any = ???
applyToList(f _)(List(1, 2, 3, 4))

But notice the need for an explicit eta-conversion f _, because the Scala compiler doesn't really know, that F is a function.

You can change the definition to return Option[R], because it's impossible to know at compile-time if the provided List will fit the function or not. For example, the argument list may have a non-matching number of elements. But if this is for unit-tests, you may as well just throw an exception.

Kolmar
  • 14,086
  • 1
  • 22
  • 25
1

If you can't modify f() then you're kinda stuck. One (not terrific) thing you could do is write an implicit translator.

Let's pretend that f() takes 4 Int args.

implicit class Caller4[A,R](func: (A,A,A,A)=>R) {
  def call(as :A*) :R = func(as(0),as(1),as(2),as(3))
}

(f _).call(1 to 4:_*)    //call f(1,2,3,4)

The good new is that this will work for any/all methods that take 4 parameters of the same type.

The bad news is that you need a different translator for every required arity, and the compiler won't catch it if you invoke call() with too few parameters.

jwvh
  • 50,871
  • 7
  • 38
  • 64