Two ways of currying in Scala; what's the use-case for each?
Asked Answered
B

3

84

I am having a discussion around Multiple Parameter Lists in the Scala Style Guide I maintain. I've come to realize that there are two ways of currying, and I'm wondering what the use cases are:

def add(a:Int)(b:Int) = {a + b}
// Works
add(5)(6)
// Doesn't compile
val f = add(5)
// Works
val f = add(5)_
f(10) // yields 15

def add2(a:Int) = { b:Int => a + b }
// Works
add2(5)(6)
// Also works
val f = add2(5)
f(10) // Yields 15
// Doesn't compile
val f = add2(5)_

The style guide incorrectly implies these are the same, when they are clearly not. The guide is trying to make a point about created curried functions, and, while the second form is not "by-the-book" currying, it's still very similar to the first form (though arguably easier to use because you don't need the _)

From those that use these forms, what's the consensus on when to use one form over the other?

Batholith answered 6/2, 2011 at 17:52 Comment(0)
R
140

Multiple Parameter List Methods

For Type Inference

Methods with multiple parameter sections can be used to assist local type inference, by using parameters in the first section to infer type arguments that will provide an expected type for an argument in the subsequent section. foldLeft in the standard library is the canonical example of this.

def foldLeft[B](z: B)(op: (B, A) => B): B

List("").foldLeft(0)(_ + _.length)

If this were this written as:

def foldLeft[B](z: B, op: (B, A) => B): B

One would have to provide more explicit types:

List("").foldLeft(0, (b: Int, a: String) => a + b.length)
List("").foldLeft[Int](0, _ + _.length)

For fluent API

Another use for multiple parameter section methods is to create an API that looks like a language construct. The caller can use braces instead of parentheses.

def loop[A](n: Int)(body: => A): Unit = (0 until n) foreach (n => body)

loop(2) {
   println("hello!")
}

Application of N argument lists to method with M parameter sections, where N < M, can be converted to a function explicitly with a _, or implicitly, with an expected type of FunctionN[..]. This is a safety feature, see the change notes for Scala 2.0, in the Scala References, for an background.

Curried Functions

Curried functions (or simply, functions that return functions) more easily be applied to N argument lists.

val f = (a: Int) => (b: Int) => (c: Int) => a + b + c
val g = f(1)(2)

This minor convenience is sometimes worthwhile. Note that functions can't be type parametric though, so in some cases a method is required.

Your second example is a hybrid: a one parameter section method that returns a function.

Multi Stage Computation

Where else are curried functions useful? Here's a pattern that comes up all the time:

def v(t: Double, k: Double): Double = {
   // expensive computation based only on t
   val ft = f(t)

   g(ft, k)
}

v(1, 1); v(1, 2);

How can we share the result f(t)? A common solution is to provide a vectorized version of v:

def v(t: Double, ks: Seq[Double]: Seq[Double] = {
   val ft = f(t)
   ks map {k => g(ft, k)}
}

Ugly! We've entangled unrelated concerns -- calculating g(f(t), k) and mapping over a sequence of ks.

val v = { (t: Double) =>
   val ft = f(t)
   (k: Double) => g(ft, k)       
}
val t = 1
val ks = Seq(1, 2)
val vs = ks map (v(t))

We could also use a method that returns a function. In this case its a bit more readable:

def v(t:Double): Double => Double = {
   val ft = f(t)
   (k: Double) => g(ft, k)       
}

But if we try to do the same with a method with multiple parameter sections, we get stuck:

def v(t: Double)(k: Double): Double = {
                ^
                `-- Can't insert computation here!
}
Radarman answered 6/2, 2011 at 22:22 Comment(10)
Great answers; wish I had more upvotes than just one. I'm going to digest and apply to the style guide; if I'm successfully, this is the chosen answers…Batholith
You might want to correct your loop example to: def loop[A](n: Int)(body: => A): Unit = (0 until n) foreach (n => body)Mytilene
This doesn't compile: val f: (a: Int) => (b: Int) => (c: Int) = a + b + cPostoperative
"Application of N argument lists to method with M parameter sections, where N < M, can be converted to a function explicitly with a _, or implicitly, with an expected type of FunctionN[..]." <br/> Shouldn't it be FunctionX[..], where X = M-N ?Clomp
"This doesn't compile: val f: (a: Int) => (b: Int) => (c: Int) = a + b + c" I don't think "f: (a: Int) => (b: Int) => (c: Int)" is the right syntax. Probably retronym meant "f: Int => Int => Int => Int". Because => is right associative, this is actually "f: Int => (Int => (Int => Int))". So f(1)(2) is of type Int => Int (that is, the inner most bit in f's type)Clomp
@retronym, in def loop[A](n: Int)(body: => A): Unit, why is body's type => A? I believe it means that body is not strictly evaluated until run-time.Enthusiast
you mean b + a.length not a + b.length. a is int, b is string. It doesn't let me edit less than 6 charactersClimax
Why do we have to provide more explicit types when it is written def foldLeft[B](z: B, op: (B, A) => B): B?Pasquinade
sad that Scala method can not curried. Function loses parameter names and default value, which is not as usable as method.Danford
I cannot understand why Scala needs assistant in List("").foldLeft(0, _ + _.length) - it's obvious that B is Int from from the 1st arg. Is it due to ambiguous 0 literal (different integer types)? In Haskell you need to help to understand what 0 is like 0::Word8 for ex.Tuberculate
A
16

You can curry only functions, not methods. add is a method, so you need the _ to force its conversion to a function. add2 returns a function, so the _ is not only unnecessary but makes no sense here.

Considering how different methods and functions are (e.g. from the perspective of the JVM), Scala does a pretty good job blurring the line between them and doing "The Right Thing" in most cases, but there is a difference, and sometimes you just need to know about it.

Athanor answered 6/2, 2011 at 18:10 Comment(2)
This makes sense, so what do you call the form def add(a:Int)(b:Int) then? What's the term/phrase describing the difference between that def and def add(a:Int, b:Int)?Batholith
@Batholith the first one is a method with multiple parameter lists, the second one a method with one parameter list.Krouse
M
5

I think it helps to grasp the differences if I add that with def add(a: Int)(b: Int): Int you pretty much just define a method with two parameters, only those two parameters are grouped into two parameter lists (see the consequences of that in other comments). In fact, that method is just int add(int a, int a) as far as Java (not Scala!) is concerned. When you write add(5)_, that's just a function literal, a shorter form of { b: Int => add(1)(b) }. On the other hand, with add2(a: Int) = { b: Int => a + b } you define a method that has only one parameter, and for Java it will be scala.Function add2(int a). When you write add2(1) in Scala it's just a plain method call (as opposed to a function literal).

Also note that add has (potentially) less overhead than add2 has if you immediately provide all parameters. Like add(5)(6) just translates to add(5, 6) on the JVM level, no Function object is created. On the other hand, add2(5)(6) will first create a Function object that encloses 5, and then call apply(6) on that.

Marasmus answered 7/2, 2011 at 16:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.