Search code examples
scalaparametersdslfunction-calls

'Spread' parameters in Scala?


Is there any way to call a Scala function that takes individual parameters, given an array (similar to JavaScript Spreads in ECMAScript 6)?

ys = [10.0, 2.72, -3.14]
f(x, ...ys);

The cleanest syntax would be:

f(1, ys)

but that does not appear to be possible. Even f(1, ys:_*) does not work (and neither does f(ys:_*), as the compiler reports too few parameters - only the first one is filled).

Example:

def f (a:Int, b:Float, c:Float, d:Float): String

val x  = 1
val ys = List(10.0, 2.72, -3.14)  // note: non-Objects
val result = f(x, ys)  // intuitive, but doesn't work

Use Case: injecting test data (from collections) into existing methods that accept individual parameters. As these are test cases, it's quite alright if the #params in ys doesn't match up and that gives either a runtime error or incorrect result.

The question is whether Scala allows a clean syntax for calling a function that takes individual parameters, given a collection of parameters -- not whether it is a good design (although opinions are certainly welcome).


Solution

  • Passing off a list as a tuple is not easy, because the types don't match very well (more on this later). With enough shoehorning and lubricating anything fits though:

    "My hack" should {
      "allow a function to be called with Lists" in {
    
        def function(bar:String, baz:String)= bar+baz
    
    
        //turn the function into something that accepts a tuple
        val functionT = function _
        val functionTT = functionT.tupled
    
        val arguments = List("bar", "baz")
    
        //Give the compiler a way to try and turn a list into a tuple of the size you need
        implicit def listToTuple(args:List[String]) = args match { 
          case List(a, b) => (a, b)
          case _ => throw IllegalArgumentException("Trying to pass off %s as a pair".format(args))
        }
    
        //Shove it in and hope for the best at runtime
        val applied = functionTT(arguments)
        applied === "barbaz"
      }
    }
    

    You can extend this approach by adding the additional arguments to the list, or by Schönfinkeling the arguments in two different groups. I wouldn't go that way.

    From my remarks you might have noticed that I don't like the design that causes this question to pop up. The code I showed is essentially code that is wrapping the function in a facade anyway. Why not do it properly?

    Looking at Spray you might see that their complete method accepts a ton of different parameters implicitly. The nifty trick they've used for this they've named the Magnet Pattern. You could do the same thing and introduce implicit conversions to your magnet for different tuples you choose to accept.