Currying in Scala

Versions: Scala 2.12.1

Scala is a hybrid language implementing both functional and object-oriented features. One of them that merits to be analyzed is currying.

This post composed of 4 sections focused on functions currying. The first part shortly explains what it is. The next one presents how the currying can be used in Scala. The third part focuses more on the aspect of default arguments. The final section contains some tests showing the practical uses of currying.

Currying definition

Accordingly to the Wikipedia's definition, the currying is:

" the technique of translating the evaluation of a function that takes multiple arguments (or a tuple of arguments) into evaluating a sequence of functions, each with a single argument.

If we would like to illustrate that, we could tell that currying consists on transforming any function composed of multiple parameters to a sequence of functions where each function has a single one parameter: f(param1, param2) is from that decomposed to following functions: f1 = f(param1) and result = f1(param2).

Currying in Scala code

More concretely in Scala, the currying can be written as:

def multiply(multiplier1: Int)(multiplier2: Int)
val multiplyBy2 = multiply(2)
val result2multipliedBy4 = multiplyBy2(4)

The version above is called shorthand because Scala has also more verbose style where the multiply method could be written like this:

def multiply(multiplier1: Int): (Int => Int) = {
  (multiplier2: Int) => {
    multiplier1 * multiplier2
  }
}
val multiplyBy2 = multiply(2)
val result5multipliedBy4 = multiplyBy2(4)

As you can see, we declare a method returning a method taking an Int and returning another Int. It looks simple for the function with 2 parameters but you can simply discover what happens if there would be more: (Int => Int => Int => Int => Int ....). This mode of writing can sometimes appear to be too verbose.

Another way to curry a function consists on the use of FunctionX's (where X is the arity [number of arguments]) curried method:

val concatenate = (separator: String, word1: String, word2: String) => s"$word1$separator$word2"
val concatenateCurried: (String) => (String) => (String) => (String) = concatenate.curried

At this occasion you'd also notice that any function can be uncurried with scala.Function.uncurried method. It applies only to the functions of the arity between 2 and 5.

The currying applies to any Scala's function and it's similar to other concept coming from functional programming, the partially applied functions. But to not introduce too many new topics at once, they'll be explained in one of further posts.

Currying and default arguments

It's important to highlight the fact that the currying doesn't work well with the default arguments of the method. Thus, the following code won't compile:

def concatenateWords(word1: String)(separator: String = ","): String = {
s"$word1$separator"
}
val concatenateWithThePrefix = concatenateWords("test")
val a = concatenateWithThePrefix()

The compilation error was:

Error:(27, 50) missing argument list for method concatenateWords in class ScalableBloomFilterTest
Unapplied methods are only converted to functions when a function type is expected.
You can make this conversion explicit by writing `concatenateWords _` or `concatenateWords(_)(_)` instead of `concatenateWords`.
  val concatenateWithThePrefix = concatenateWords("the")

The solution for that could be the use of apply method defined inside a class, as shown:

class WordsConcatenator(word1: String) {
  def apply(separator: String = " "): String = {
    s"$word1$separator"
  }
}
def wordsConcatenatorWithDefault = new WordsConcatenator("the")
def concatenateWithThePrefix = wordsConcatenatorWithDefault

Currying examples

The tests in this section contain some examples of currying:

describe("Scala") {
  it("should uncurry the function with the 3 parameters") {
    def concatenate(separator: String)(word1: String)(word2: String): String = {
      s"$word1$separator$word2"
    }

    val uncurriedConcatenate = Function.uncurried(concatenate _)
    val aAndBCommaSeparated = uncurriedConcatenate(",", "A", "B")

    aAndBCommaSeparated shouldEqual "A,B"
  }
  it("should apply currying to the function with 3 parameters through .curried helper function") {
    val concatenate = (separator: String, word1: String, word2: String) => s"$word1$separator$word2"
    val concatenateCurried: (String) => (String) => (String) => (String) = concatenate.curried
    val concatenateWithComma: (String) => (String) => (String) = concatenateCurried(",")
    val concatenateWithCommaAndA: (String) => (String) = concatenateWithComma("A")

    val aAndBCommaSeparated = concatenateWithCommaAndA("B")

    aAndBCommaSeparated shouldEqual "A,B"
  }
  it("should apply currying to the function without default arguments") {
    def concatenate(separator: String)(word1: String)(word2: String): String = {
      s"$word1$separator$word2"
    }
    def concatenateWithComma = concatenate(",") _

    val aAndBCommaSeparated = concatenateWithComma("A")("B")

    aAndBCommaSeparated shouldEqual "A,B"
  }
  it("should define currying in more verbose manner") {
    def concatenate(separator: String): (String => String => String) = {
      (word1: String) => {
        (word2: String) => {
          s"$word1$separator$word2"
        }
      }
    }
    val concatenateWithComma = concatenate(",")

    val aAndBCommaSeparated = concatenateWithComma("A")("B")

    aAndBCommaSeparated shouldEqual "A,B"
  }
  it("should not keep the default arguments of the method") {
    class WordsConcatenator(word1: String) {
      def apply(separator: String = " "): String = {
        s"$word1$separator"
      }
    }
    def wordsConcatenatorWithDefault = new WordsConcatenator("the")
    def concatenateWithThePrefix = wordsConcatenatorWithDefault

    val end = concatenateWithThePrefix()

    end shouldEqual "the"
  }
}

Currying is a useful functional programming feature implemented in Scala. As presented in the first section, its goal consists on simplifying the functions by reducing the number of parameters. Two writing methods are available categorized in the post as less and more verbose. As shown in the third part, the currying applies with difficulty to the methods having default arguments. In order to use it in this kind of functions, a small hack with the use of class and its apply method is required. The last section presented some use cases of currying, focusing especially on described grammar.