scala currying by nested functions or by multiple parameter lists
Asked Answered
F

3

6

In Scala, I can define a function with two parameter lists.

def myAdd(x :Int)(y :Int) = x + y

This makes it easy to define a partially applied function.

val plusFive = myAdd(5) _

But, I can accomplish something similar by defining and returning a nested function.

  def myOtherAdd(x :Int) = {
    def f(y :Int) = x + y
    f _
  }

Cosmetically, I've moved the underscore, but this still feels like currying.

val otherPlusFive = myOtherAdd(5)

What criteria should I use to prefer one approach over the other?

Fullblooded answered 15/1, 2011 at 0:53 Comment(0)
I
10

There are at least four ways to accomplish the same thing:

def myAddA(x: Int, y: Int) = x + y
val plusFiveA: Int => Int = myAddA(5,_)

def myAddB(x: Int)(y : Int) = x + y
val plusFiveB = myAddB(5) _

def myAddC(x: Int) = (y: Int) => x + y
val plusFiveC = myAddC(5)

def myAddD(x: Int) = {
  def innerD(y: Int) = x + y
  innerD _
}
val plusFiveD = myAddD(5)

You might want to know which is most efficient or which is the best style (for some non-performance based measure of best).

As far as efficiency goes, it turns out that all four are essentially equivalent. The first two cases actually emit exactly the same bytecode; the JVM doesn't know anything about multiple parameter lists, so once the compiler figures it out (you need to help it with a type annotation on the case A), it's all the same under the hood. The third case is also extremely close, but since it promises up front to return a function and specifies it on the spot, it can avoid one internal field. The fourth case is pretty much the same as the first two in terms of work done; it just does the conversion to Function1 inside the method instead of outside.

In terms of style, I suggest that B and C are the best ways to go, depending on what you're doing. If your primary use case is to create a function, not to call in-place with both parameter lists, then use C, because it tells you what it's going to do. (This version is also particularly familiar to people coming from Haskell, for instance.) On the other hand, if you are mostly going to call it in place but will only occasionally curry it, then use B. Again, it says more clearly what it's expected to do.

Intisar answered 15/1, 2011 at 3:42 Comment(2)
Thanks, Rex. I was unaware of approaches A and C. Your answer is really helpful.Fullblooded
I normally use the approach which returns a function def myAddC(x: Int) = (y: Int) => x + y val plusFiveC = myAddC(5) , but the currying approach does not come in natural for me to write, though i understand when reading them. def myAddB(x: Int)(y : Int) = x + y val plusFiveB = myAddB(5) _ Squaw
P
6

You could also do this:

def yetAnotherAdd(x: Int) = x + (_: Int)

You should choose the API based on intention. The main reason in Scala to have multiple parameter lists is to help type inference. For instance:

def f[A](x: A)(f: A => A) = ...
f(5)(_ + 5)

One can also use it to have multiple varargs, but I have never seen code like that. And, of course, there's the need for the implicit parameter list, but that's pretty much another matter.

Now, there are many ways you can have functions returning functions, which is pretty much what currying does. You should use them if the API should be thought of as a function which returns a function.

I think it is difficult to get any more precise than this.

Prophesy answered 15/1, 2011 at 1:4 Comment(2)
Thanks, Daniel. Your comment about type inference as the motivation for multiple parameter lists is eye-opening. This was really helpful.Fullblooded
The other motivation is for writing DSLs that integrate into the language nicely, something like using(x) { ... }. Though this does, admittedly, also make use of the type inferencing afforded to multiple blocks.Theiss
T
4

Another benefit of having a method return a function directly (instead of using partial application) is that it leads to much cleaner code when using infix notation, allowing you to avoid a bucketload of parentheses and underscores in more complex expressions.

Consider:

val list = List(1,2,3,4)

def add1(a: Int)(b: Int) = a + b
list map { add1(5) _ }    

//versus

def add2(a: Int) = a + (_: Int)
list map add2(5)
Theiss answered 24/1, 2011 at 9:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.