1

I'm getting problems to understand the currying concept, or at least the SCALA currying notation.

wikipedia says that currying is the technique of translating the evaluation of a function that takes multiple arguments into evaluating a sequence of functions, each with a single argument.

Following this explanation, are the two next lines the same for scala?

def addCurr(a: String)(b: String): String = {a + " " + b}
def add(a:String): String => String = {b => a + " " + b}

I've run both lines with the same strings a and b getting the same result, but I don't know if they are different under the hood

My way of thinking about addCurr (and currying itself) is that it is a function that receives a string parameter a, and returns another function that also receives a string parameter b and returns the string a + " " + b?

So if I'm getting right, addCurr is only syntactic sugar of the function add and both are curryed functions?

According to the previous example, the next functions are also equivalent for scala?

def add(a: String)(b: String)(c: String):String = { a + " " + b + " " + c}

def add1(a: String)(b: String): String => String = {c => a + " " + b + " " + c}

def add2(a:String): (String => (String => String)) = {b => (c => a + " " + b + " " + c)}
Darth_Tato
  • 963
  • 6
  • 10

3 Answers3

3

They have a bit different semantics, but their use-cases are mostly the same, both practically and how it looks in the code.

Currying

Currying a function in Scala in that mathematical sense is a very straightforward:

val function = (x: Int, y: Int, z: Int) => 0
// function: (Int, Int, Int) => Int = <function3>
function.curried
// res0: Int => (Int => (Int => Int)) = <function1>

Functions & methods

You seem to be confused by the fact that in Scala, (=>) functions are not the same as (def) methods. Method isn't a first-class object, while function is (i.e. it has curried and tupled methods, and Function1 has even more goodness).

Methods, however, can be lifted to functions by an operation known as eta expansion. See this SO answer for some details. You can trigger it manually by writing methodName _, or it will be done implicitly if you give a method to where a function type is expected.

def sumAndAdd4(i: Int, j: Int) = i + j + 4
// sumAndAdd4.curried // <- won't compile

val asFunction = sumAndAdd4 _ // trigger eta expansion
// asFunction: (Int, Int) => Int = <function2>
val asFunction2: (Int, Int) => Int = sumAndAdd4
// asFunction2: (Int, Int) => Int = <function2>
val asFunction3 = sumAndAdd4: (Int, Int) => Int
// asFunction3: (Int, Int) => Int = <function2>


asFunction.curried
// res0: Int => (Int => Int) = <function1>
asFunction2.curried
// res1: Int => (Int => Int) = <function1>
asFunction3.curried
// res2: Int => (Int => Int) = <function1>
{sumAndAdd4 _}.tupled // you can do it inline too
// res3: Int => (Int => Int) = <function1>

Eta expansion of multiple parameter list

Like you might expect, eta expansion lifts every parameter list to its own function

def singleArgumentList(x: Int, y: Int) = x + y
def twoArgumentLists(x: Int)(y: Int) = x + y

singleArgumentList _ // (Int, Int) => Int
twoArgumentLists _ // Int => (Int => Int) - curried!

val testSubject = List(1, 2, 3)

testSubject.reduce(singleArgumentList) // Int (6)
testSubject.map(twoArgumentLists) // List[Int => Int]

// testSubject.map(singleArgumentList) // does not compile, map needs Int => A
// testSubject.reduce(twoArgumentLists) // does not compile, reduce needs (Int, Int) => Int

But it's not that currying in mathematical sense:

def hmm(i: Int, j: Int)(s: String, t: String) = s"$i, $j; $s - $t"

{hmm _} // (Int, Int) => (String, String) => String

Here, we get a function of two arguments, returning another function of two arguments.

And it's not that straightforward to specify only some of its argume

val function = hmm(5, 6) _ // <- still need that underscore!

Where as with functions, you get back a function without any fuss:

val alreadyFunction = (i: Int, j: Int) => (k: Int) => i + j + k

val f = alreadyFunction(4, 5) // Int => Int

Do which way you like it - Scala is fairly un-opinionated about many things. I prefer multiple parameter lists, personally, because more often than not I'll need to partially apply a function and then pass it somewhere, where the remaining parameters will be given, so I don't need to explicitly do eta-expansion, and I get to enjoy a terser syntax at method definition site.

Community
  • 1
  • 1
Oleg Pyzhcov
  • 7,323
  • 1
  • 18
  • 30
0

Curried methods are syntactic sugar, you were right about this part. But this syntactic sugar is a bit different. Consider following example:

def addCur(a: String)(b: String): String = { a + b }

def add(a: String): String => String = { b => a + b }

val functionFirst: String => String = add("34")
val functionFirst2 = add("34")_
val functionSecond: String => String = add("34")

Generaly speaking curried methods allows for partial application and are necessary for the scala implicits mechanism to work. In the example above i provided examples of usage, as you can see in the second one we have to use underscore sign to allow compiler to do the "trick". If it was not present you would receive error similar to the following one:

Error:(75, 19) missing argument list for method curried in object XXX Unapplied methods are only converted to functions when a function type is expected. You can make this conversion explicit by writing curried_ or curried(_)(_) instead of curried.

L.Lampart
  • 755
  • 5
  • 10
  • Is there a mistake in your code? You defined functionFirst2, yet you don't do anything with it. functionFirst and functionSecond are identical. Did you mean something else? – boggy Jan 14 '17 at 02:49
  • yes, I know that addCur returns a string, so I can't do `val x = addCur("apple")`, because the compile expect to return another function to make a partial application and this function returns a String, but this is valid `val x = addCur("apple")_`. With add, this is valid `val x = add("apple")` because it return a function. That's correct?? – Darth_Tato Jan 14 '17 at 02:55
0

Your question interested me so I tried this out my self. They actually desugar down to some very different constructs. Using

def addCurr(a: String)(b: String): String = {a + " " + b}

This actually compiles to

def addCurr(a: String, b: String): String = {a + " " + b}

So it completely removes any currying effect, making it a regular arity-2 method. Eta expansion is used to allow you to curry it.

def add(a:String): String => String = {b => a + " " + b}

This one works as you would expect, compiling to a method that returns a Function1[String,String]

puhlen
  • 8,400
  • 1
  • 16
  • 31
  • So they are not the same? I mean, both do the same but if they compile differently, one must be better in some aspect than the other. What do you think? – Darth_Tato Jan 14 '17 at 02:47
  • I haven't done any testing, but I suspect that the form `def curr(a: String)(b: String):String` would normally be better (Although it might be case-dependent). Intuitively, because the case where you provide all the arguments and don't curry the function, it will just be a regular method call with no additional object creation or indirection, and because the language designers chose not to compile it to the other form you posted. – puhlen Jan 14 '17 at 03:04
  • Thanks @Puhlen. I do prefer the expression `def curr(a: String)(b: String)..` – Darth_Tato Jan 14 '17 at 20:32