3

Consider a function that returns another function:

def prepareFunction(args: List[Any]): String => Unit = {
  println(s"Slow processing of $args...")
  val results = args.map(a => s"processed $a")

  def doSomething(s: String): Unit = {
    println(s"Do something quick with $s and $results")
  }

  doSomething
}

The idea here is: An outer function does some heavy processing and returns a inner function that uses variables defined in the enclosing scope:

val doSomethingWithArgs = prepareFunction(List("arg1", "arg2", 3)) 
   //> Slow processing of List(arg1, arg2, 3)...
doSomethingWithArgs("abc")
   //> Do something quick with abc and List(processed arg1, processed arg2, processed 3)
doSomethingWithArgs("cde") 
   //> Do something quick with cde and List(processed arg1, processed arg2, processed 3)

Note that the outer function is evaluated only once.

With multiple parameter lists and Scala's Currying syntax we can write something similar:

def prepareCurried(args: List[Any])(s: String): Unit = {
  println(s"Slow processing of $args")
  val results = args.map(a => s"processed $a")

  def doSomething(s: String): Unit = {
    println(s"Do something quick with $s and $results")
  }

  doSomething(s)
}  

But the "outer" function gets evaluated every time:

val doSomethingWithOtherArgs = prepareCurried(List(4, 5, 6)) _
doSomethingWithOtherArgs("abc")
  //> Slow processing of List(4, 5, 6)
  //> Do something quick with abc and List(processed 4, processed 5, processed 6)
doSomethingWithOtherArgs("cde")
  //> Slow processing of List(4, 5, 6)
  //> Do something quick with cde and List(processed 4, processed 5, processed 6)

My question is, can I somehow force prepareCurried to be evaluated on the line bellow?

val doSomethingWithOtherArgs = prepareCurried(List(4, 5, 6)) _

To put it differently, is it possible to get the same effect as "evaluation on definition" when partially applying a function with multiple parameter lists?

Community
  • 1
  • 1
Anthony Accioly
  • 21,918
  • 9
  • 70
  • 118
  • 4
    That is just how a curried function is supposed to behave by definition and I can not think of any reason as to why you want to change that. You already have the other choice in your pattern 1. – sarveshseri Feb 21 '17 at 15:02
  • Hey @Sarvesh, no particular reason, I'm just wondering about the symmetry between currying and functions that returns functions. – Anthony Accioly Feb 21 '17 at 18:15

2 Answers2

10

This is a case where breaking out reify from Scala's reflection API can be helpful for getting a sense of how the syntax is being desugared:

scala> import scala.reflect.runtime.universe.{ reify, showCode }
import scala.reflect.runtime.universe.{reify, showCode}

scala> showCode(reify(prepareCurried(List(4, 5, 6)) _).tree)
res0: String =
{
  val eta$0$1 = List.apply(4, 5, 6);
  ((s) => $read.prepareCurried(eta$0$1)(s))
}

There's simply no way to get the preparedCurried(eta) part without having an s. This makes sense when you think about how methods with multiple parameter lists are represented on the JVM (i.e. all the parameter lists are just smashed together). As the commenter above notes, if you want to be able to separate the preparation of the second function from its application, you'll need to return a function explicitly.

Travis Brown
  • 138,631
  • 12
  • 375
  • 680
  • Interesting. Out of curiosity I've tried `showCode(reify(List(1, 2, 3).foldLeft(0) _).tree)` with analogue results (`((op) => eta$0$1.foldLeft(0)(op))`). Makes sense, so, what we are really getting are "wrapper" functions around the original function right? `prepareCurried _` is actually `((args) => ((s) => prepareCurried(args)(s)))`. – Anthony Accioly Feb 21 '17 at 18:30
  • 1
    @AnthonyAccioly Right, but there's not really an "original function" in the `Function1` sense—`prepareCurried _` is just syntax that desugars to the anonymous function definition. – Travis Brown Feb 21 '17 at 19:11
2

I think it will be easier to see what's going on if we write the internals of prepareFunction as function literals instead of defs.

This would be what your method originally looked like:

def prepareFunction(args: List[Any]): String => Unit = {
  println(s"Slow processing of $args...")
  val results = args.map(a => s"processed $a")

  (s: String) => println(s"Do something quick with $s and $results")
}

And this is "equivalent" to your curried method:

def prepareCurried(args: List[Any]): Unit = (s: String) => {
  println(s"Slow processing of $args")
  val results = args.map(a => s"processed $a")

  println(s"Do something quick with $s and $results")
}

Now it's easy to see that in prepareCurried the slow processing is part of the function that is being returned.

There's nothing you can do about it. That's the flexibility you lose when using multiple parameter lists over explicitly writing the functions. You have to choose for each use case which is better suited.

Jasper-M
  • 14,966
  • 2
  • 26
  • 37