Search code examples
scalafunctional-programmingcurrying

scala parameter partial application


I wonder why Scala does not use the same style of partial application for functions as other functional programming languages?

What's the benefit, or idea behind multiple parameter lists?

Does it have anything to do with the fact that Scala is based on dot calculus, or is it an optimization for the JVM?

To me it adds issues, if somebody defines a function as:

def f(x: Int, y: Int): Int

and not as:

def f(x: Int)(y: Int): Int

I cannot do partial application, simple way.

Is it a good practice to define most functions like that (is it a default approach):

def f(x: Int)(y: Int)(z: Int): Int

Solution

  • Odersky et al in Programming In Scala cover it.

    Scala's syntax for partially applied functions highlights a difference in the design trade-offs of Scala and classical functional languages, such as Haskell or ML. In these languages, partially applied functions are considered the normal case. Furthermore, these languages have a fairly strict static type system that will usually highlight every error with partial applications that you can make. Scala [bears a relation to languages such as Java] where a method that's not applied to all of its arguments is considered an error. Furthermore, the object-oriented tradition of subtyping and a universal root type accepts some programs that would be considered erroneous in classical functional languages.

    For instance, say you mistook the drop(n: Int) method of List for tail, and therefore forgot that you need to pass a number to drop. You might write println(drop). Had Scala adopted the classical functional tradition that partially applied functions are OK everywhere, this code would type check. However, you might be surprised to find out that the [side effect of this println would always be to output] <function>! [Since] the expression drop would have been treated as any function object and println takes objects of any type, this would have compiled, but it would have an unexpected result.

    To avoid situations like this, Scala normally requires you to specify function arguments that are left out explicitly, even if the indication is as simple as _. [The _ can be left off, however] only when a function type is expected.

    The f(x)(y) notation tends to be used only when there's an apparent utility for partial application (or as a signal from the author that partial application is endorsed) or in order to take advantage of the interplay with other aspects of Scala:

    • Type inference happens parameter-list by argument-list, so one can fix types based on the first argument list for later argument lists. This can save some type bounds. Consider Option.map(f).getOrElse(a) (where a can be an instance of any supertype of f's result type) vs. Option.fold(a)(f) (where the type of a constrains the result type of f).

    • In a feature likely inspired by Perl, braces ({}) can replace parens around a single argument list. This feature facilitates a syntax that looks very C-like at the call-site:

    // NB: doesn't support early exit (except by throwing an exception...)
    def myWhile(condition: => Boolean)(body: => Unit): Unit = {
      @annotation.tailrec
      def spin: Unit =
        if (condition) {
          body
          spin
        } else ()
    
      spin
    }
    
    var x: Int = 0
    myWhile (x < 4) {
      println("whee!")
      x += 1
    }