scala-logo-small

Simple Intro to Partially Applied Functions in Scala

Quick Example
Try this in the REPL. Define a general function:

scala> def replace(s: String, replaceThis: String, withThis: String) = s.replace(replaceThis, withThis)
replace: (s: String, replaceThis: String, withThis: String)String

Create a more specific function by setting some parameters to hold default values automatically:

scala> def replaceWorldWithUniverse = replace(_: String, "world", "universe")
replaceWorldWithUniverse: String => String

Now use the new function:

scala> replaceWorldWithUniverse("Hello world")
res0: String = Hello universe

Explanation

Passing parameters to a function is also called applying the function to those parameters.

You can “partially apply” any function by not supplying a full parameter list, e.g. using one or more of its parameters while leaving other parameters blank. The result of partially applying a function is a new function, which takes the parameter or parameters that you omitted, while utilizing the ones already applied.

You leave parameters blank with an underscore, followed by the type–in other words replace “s: String” with “_: String”.

When you do this, the parameters that you provided values for (replaceThis and withThis) are said to be “applied.” The resulting function is partially applied because not all parameters have been applied.

Here is another quick example.

scala> def changeForDollars(numDollars: Int, coinDenomination: Int) = (numDollars * 100) / coinDenomination
changeForDollars: (numDollars: Int, coinDenomination: Int)Int

With this function, you can get change for a dollar in quarters by passing “25” to the coinDenomination parameter.

scala> changeForDollars(1,25)
res2: Int = 4

quarters

Why not just create a quartersForDollars function, by applying 25 to the coinDenomination parameter?

scala> def quartersForDollars = changeForDollars(_: Int, 25)
quartersForDollars: Int => Int

scala> quartersForDollars(1)
res4: Int = 4

scala> quartersForDollars(10)
res5: Int = 40

Why this is useful
In the examples above it might not be that clear why this might be useful. Some might say it is nice because it saves some keystrokes. Sure it can be annoying to always type 25 in the call “changeForDollars(1, 25)” but that’s a minor advantage. Others say it is nice because it gives you a function with a better name, resulting in more readable code.

While neither of those reasons are wrong, they aren’t very satisfying because we can achieve the same results from function forwarding. i.e. I could write the quartersForDollars function like this:

scala> def quartersForDollars(d: Int) = changeForDollars(d, 25)
quartersForDollars: (d: Int)Int

The result would be the same:

scala> quartersForDollars(1)
res16: Int = 4

scala> quartersForDollars(10)
res17: Int = 40

This makes the underscore syntax and the idea of a partially-applied function seem like mere cleverness, or syntactic sugar that really should be avoided since it clutters up the language.

But this would be forgetting that functions in Scala are first-class citizens, which can be passed into other functions. This makes it possible to do a lot of things you cannot easily do in an imperative language, like Java. Read on for one example.

Internationalized Change Maker

We can use our changeForDollars function for any decimalized currency in the world because it converts the dollar value passed in to units of 100. But there are currencies in the world that aren’t decimalized. The Mauritanian Ouguiya, for example, has as its base unit the khoum, and five khoums equals one ouguiya. Even British pounds were not decimalized before 1971, so if we want to deal in Old British pounds, we would have to have the pence as the base unit, and 240 pence would equal one pound.

uk-flag

With this in mind, we need to write some new functions.

scala> def convertDollarsToCents(dollars: Int) = dollars * 100
convertDollarsToCents: (dollars: Int)Int

scala> def convertOldPoundsToPence(pounds: Int) = pounds * 240
convertOldPoundsToPence: (pounds: Int)Int

scala> def makeChange(convert: Int => Int, amount: Int, coinValue: Int) = convert(amount) / coinValue
makeChange: (convert: Int => Int, amount: Int, coinValue: Int)Int

OK, with the above three functions defined, we can now partially apply makeChange to create new functions that make change for US dollars and old British pounds:

scala> def changeForDollars = makeChange(convertDollarsToCents, _: Int, _: Int)
changeForDollars: (Int, Int) => Int

scala> def changeForOldPounds = makeChange(convertOldPoundsToPence, _: Int, _: Int)
changeForOldPounds: (Int, Int) => Int

Cool, huh? With a single makeChange function, we can create new functions for US dollars and old British pounds. In Java pre-version-8 we would actually need to write two separate makeChange methods with the same code except for the single differing line of calling a different convert method…OK, you’re right, that’s not exactly true. Actually, there was that whole S.A.M. thing but it required an interface and a class to be created and some bizarre/awkward syntax when the method was called. Java 8 is better, but it still requires the implementation of a Function interface.

Don’t forget that here in Scala, we actually haven’t been required to create any classes so far. It’s all functions, baby! And in the postscript, I want to show you something that is even cooler.

But first, we want to demonstrate our new functions:

scala> changeForDollars(1, 5)
res0: Int = 20

scala> changeForOldPounds(1, 12)
res1: Int = 20

That last example, comes from the fact that there was a 12 pence coin, historically.

Postscript
So that was all cool and everything, but here’s one better. If you are adventurous, you don’t have to explicitly define separate named functions. You can inline your functions, also using an underscore:

scala> def changeForOldPounds = makeChange({_ * 240}, _: Int, _: Int)
changeForOldPounds: (Int, Int) => Int

scala> changeForOldPounds(1, 12)
res3: Int = 20

And that shows the power of partially applied functions!

Leave a Reply

Your email address will not be published. Required fields are marked *