0

Whilst I understand what a partially applied/curried function is, I still don't fully understand why I would use such a function vs simply overloading a function. I.e. given:

def add(a: Int, b: Int): Int = a + b
val addV = (a: Int, b: Int) => a + b

What is the practical difference between

def addOne(b: Int): Int = add(1, b)

and

def addOnePA = add(1, _:Int)
// or currying
val addOneC = addV.curried(1)

Please note I am NOT asking about currying vs partially applied functions as this has been asked before and I have read the answers. I am asking about currying/partially applied functions VS overloaded functions

  • Duplicate of several other questions – The Archetypal Paul Mar 18 '16 at 08:06
  • Possible duplicate of [Usefulness (as in practical applications) of Currying v.s. Partial Application in Scala](http://stackoverflow.com/questions/8063325/usefulness-as-in-practical-applications-of-currying-v-s-partial-application-i) – The Archetypal Paul Mar 18 '16 at 08:06
  • I'm not asking about currying vs partially applied functions, I'm asking about currying/partially applied functions vs overloaded functions –  Mar 18 '16 at 14:40

2 Answers2

1

The difference in your example is that overloaded function will have hardcoded value 1 for the first argument to add, i.e. set at compile time, while partially applied or curried functions are meant to capture their arguments dynamically, i.e. at run time. Otherwise, in your particular example, because you are hardcoding 1 in both cases it's pretty much the same thing.

You would use partially applied/curried function when you pass it through different contexts, and it captures/fills-in arguments dynamically until it's completely ready to be evaluated. In FP this is important because many times you don't pass values, but rather pass functions around. It allows for higher composability and code reusability.

yǝsʞǝla
  • 16,272
  • 2
  • 44
  • 65
1

There's a couple reasons why you might prefer partially applied functions. The most obvious and perhaps superficial one is that you don't have to write out intermediate functions such as addOnePA.

List(1, 2, 3, 4) map (_ + 3) // List(4, 5, 6, 7)

is nicer than

def add3(x: Int): Int = x + 3
List(1, 2, 3, 4) map add3

Even the anonymous function approach (that the underscore ends up expanding out to by the compiler) feels a tiny bit clunky in comparison.

List(1, 2, 3, 4) map (x => x + 3)

Less superficially, partial application comes in handy when you're truly passing around functions as first-class values.

val fs = List[(Int, Int) => Int](_ + _, _ * _, _ / _)
val on3 = fs map (f => f(_, 3)) // partial application
val allTogether = on3.foldLeft{identity[Int] _}{_ compose _}

allTogether(6) // (6 / 3) * 3 + 3 = 9

Imagine if I hadn't told you what the functions in fs were. The trick of coming up with named function equivalents instead of partial application becomes harder to use.

As for currying, currying functions often lets you naturally express transformations of functions that produce other functions (rather than a higher order function that simply produces a non-function value at the end) which might otherwise be less clear.

For example,

def integrate(f: Double => Double, delta: Double = 0.01)(x: Double): Double = {
  val domain = Range.Double(0.0, x, delta)
  domain.foldLeft(0.0){case (acc, a) => delta * f(a) + acc      
}

can be thought of and used in the way that you actually learned integration in calculus, namely as a transformation of a function that produces another function.

def square(x: Double): Double = x * x

// Ignoring issues of numerical stability for the moment...
// The underscore is really just a wart that Scala requires to bind it to a val
val cubic = integrate(square) _ 
val quartic = integrate(cubic) _
val quintic = integrate(quartic) _

// Not *utterly* horrible for a two line numerical integration function
cubic(1) // 0.32835000000000014
quartic(1) // 0.0800415
quintic(1) // 0.015449626499999999

Currying also alleviates a few of the problems around fixed function arity.

implicit class LiftedApply[A, B](fOpt: Option[A => B]){ 
  def ap(xOpt: Option[A]): Option[B] = for {
    f <- fOpt
    x <- xOpt
  } yield f(x)
}

def not(x: Boolean): Boolean = !x
def and(x: Boolean)(y: Boolean): Boolean = x && y
def and3(x: Boolean)(y: Boolean)(z: Boolean): Boolean = x && y && z

Some(not _) ap Some(false) // true
Some(and _) ap Some(true) ap Some(true) // true
Some(and3 _) ap Some(true) ap Some(true) ap Some(true) // true

By having curried functions, we've been able to "lift" a function to work on Option for as many arguments as we need. If our logic functions had not been curried, then we would have had to have separate functions to lift A => B to Option[A] => Option[B], (A, B) => C to (Option[A], Option[B]) => Option[C], (A, B, C) => D to (Option[A], Option[B], Option[C]) => Option[D] and so on for all the arities we cared about.

Currying also has some other miscellaneous benefits when it comes to type inference and is required if you have both implicit and non-implicit arguments for a method.

Finally, the answers to this question list out some more times you might want currying.

badcook
  • 3,699
  • 14
  • 25