In Progress
Unit 1, Lesson 21
In Progress

Partial Function Application

Functional programming techniques are becoming more and more mainstream. And while Ruby is generally thought of as an object-oriented language, it is designed to enable functional approaches as well.

Two important and related tools from the functional world are currying and partial function application. In today’s episode Joe Leo, co-author of The Well-Grounded Rubyist, joins us to show Ruby’s built-in support for currying and partial application. Enjoy!

Video transcript & code

Currying and Partial Function Application in Ruby

Introduction


The Well-Grounded Rubyist, 3rd Edition

Today I'm going to discuss a bit of functional programming, a topic I dive into in the latest edition of The Well-Grounded RubyistObject-oriented programming serves as the foundation of Ruby. But Ruby has always had language features to support functional programming. In fact, Matz is quick to cite Lisp - a functional language - as his biggest influence when he created Ruby.

In the last several years, the Ruby core team has ramped up it's support of FP. Today I'm going to explain two related and helpful concepts: currying and partial function application. My goal is to give you more options when deciding how to design your Ruby programs.

Let's start with currying. The concept of currying comes from mathematician Haskell Brooks Curry. And yes, that is also where the name for the Haskell programming language came from. In mathematics and in computer science, currying is the act of splitting one function with multiple arguments into multiple functions, each with one argument. Here we see a simple add function that takes two arguments, a and b, and adds them together. This is one function with two arguments.


  add = -> (a, b) { a + b }

When we call add, we simply pass in the two arguments and the sum is returned. Let's try this out. Notice that I'm using the "dot parentheses" syntax, which is shorthand for "dot call" with parentheses.


  add = -> (a, b) { a + b }
  add.(1,3) # => 4
  add.(5,6) # => 11

By contrast curried_add is actually two functions chained together. Each individual function takes one argument.


  curried_add = -> (a) { -> (b) { a + b } }

We have to call this function differently. We can't simply call curried_add with two arguments because we'll get an ArgumentError.

We need to call curried_add with one argument, and then call the function that is returned with the other argument.


  curried_add = -> (a) { -> (b) { a + b } }
  curried_add.(1,3) # ArgumentError: wrong number of arguments (given 2, expected 1)
  curried_add.(1).(3) # => 4

Both add and curried_add are valid ruby syntax, and both are lambdas. The second function is a curried version of the first.


  add = -> (a, b) { a + b } # => #<Proc:0x0000560ebc4b14e8@(irb):1 (lambda)>
  curried_add = -> (a) { -> (b) { a + b } } # => #<Proc:0x0000558b006eb820@(irb):2 (lambda)>

In Ruby, we can create the functional equivalent of curried_add by using the curry method.

Notice that the curry method simply returns a lambda, just like our stabby lambda examples above.

In Ruby, the curry method gives us some additional flexibility. We can now call curried_add with one or two arguments. Let's try calling it both ways.


  curried_add = add.curry # => #
  curried_add.(1,3)
  curried_add.(1).(3)

The result is the same and there are no ArgumentErrors. This becomes important as we move into partial function application.


  curried_add = add.curry # => #
  curried_add.(1,3) # => 4
  curried_add.(1).(3) # => 4

Partial function application is a programming technique used when we know some, but not all, of a function's arguments. The function is evaluated with the given arguments and a new function is returned that will accept the rest of the arguments. This can be done repeatedly until all of the required arguments are supplied. Let's use our original add function as an example.


  add = -> (a, b) { a + b }

We've just created a function using the "stabby lambda" syntax. Just like last time, my add function takes two arguments, a and b, and adds them together. What if I know the value of a now but not the value of b?


  add = -> (a, b) { a + b } # => #<Proc:0x0000560ebc4d8b60@(irb):13 (lambda)>

If I pass in that value for a and use partial function application, I will get another function with the value for a applied. I now have a function that takes an argument b and adds 1 to it.


  add_one = -> (b) { 1 + b }

The curry method obviates the need to write out most of the function syntax we've seen so far. Thus add.curry can give us the partially applied function add_one.


  add = -> (a, b) { a + b }
  add_one = add.curry.(1)
  add_one.(5)

When we call add_one, we only need to give it one argument. This technique is powerful both because of its conciseness and because of the versatility it allows us.


  add = -> (a, b) { a + b }
  add_one = add.curry.(1)
  add_one.(5) # => 6

A common use case of currying and partial function application is creating generic functions that can be reused. Here's a function that takes a value, x, and an array and finds multiples of the value within the array.


  find_multiples = -> (x, arr) {
    arr.select { |el| el % x == 0 }
  }

Let's call find_multiples a couple of times.

The first time we're looking for multiples of 3 in a given array. The second time we'll look for multiples of 5 with a different array.


  find_multiples = lambda do |x, arr|
    arr.select { |el| el % x == 0 }
  end

  find_multiples.(3, [1,3,5,7,9,12]) # => [3, 9, 12]
  find_multiples.(5, [3,6,9,12,15]) # => [15]

So what if we want to use find_multiples in an object where x is already known? We can refine find_multiples with partial function application using the curry method. find_multiples_of is the curried form of find_multiples. Now we can pass in the first argument and return a function that takes the remaining argument, an array.


  find_multiples_of = find_multiples.curry

Let's make equivalents of the find_multiples functions above. We get a pair of new functions that we've created with the help of our generic find_multiples function.


  find_multiples_of_3 = find_multiples_of.(3)
  find_multiples_of_5 = find_multiples_of.(5)

Our new functions now simply take an array.


  find_multiples_of_3.([1,3,5,7,9,12])
  find_multiples_of_5.([3,6,9,12,15])


  find_multiples_of_3.([1,3,5,7,9,12]) # => [3, 9, 12]
  find_multiples_of_5.([3,6,9,12,15]) # => [15]

We can call curried or partially applied functions a number of ways. Let's turn again to our simple add function, this time adding a third argument.


  add = -> (a, b, c) { a + b + c }
  fun = add.curry

Calling curry with no arguments simply returns a lambda.


  add = -> (a, b, c) { a + b + c }
  fun = add.curry # =>  #

We can evaluate the lambda all at once...


  fun.(1,2,3) # => 6

...or chain several calls together.


  fun.(1).(2).(3) # => 6

Of course if we pass in anything less than the function's arity, we're back to partial function application.


  fun2 = fun.(1) # => #
  fun2.(2,3) # => 6

Let's make this a little more interesting. We've already seen functions that sum two or three variables. Now let's create a function that sums up an arbitrary number of variables. Using "splat nums," we tell our sum_all function to add up however many arguments we pass in.

We can call it with two arguments or ten arguments and sum_all will do its job.


  sum_all = -> (*nums) { nums.reduce(:+) }
  sum_all.(1,2) # => 3
  sum_all.(1,2,3,4,5,6,7,8,9,10) # => 55

sum_all is a handy function, but it's a little greedy. We can't use partial function application with sum_all because no matter how few arguments we supply it will still execute the function. However, we can curry sum_all and pass in an optional minimal arity argument. Once we curry a function in this way, the curried object will only be evaluated once the required number of arguments has been supplied. Let's try this with sum_all by calling curry and supplying a minimum arity of 4. We'll store the result in sum_at_least_four. By doing this we are telling Ruby not to evaluate the function until a minimum of four arguments are passed into that function.


  sum_at_least_four = sum_all.curry(4)

Now when we call sum_at_least_four with only two arguments, we are partially applying sum_at_least_four.


  sum_at_least_four = sum_all.curry(4)
  sum1 = sum_at_least_four.(3,4)

We get our familiar lambda back and we can re-use it. curry has given us our generic function superpowers back!


  sum_at_least_four = sum_all.curry(4)
  sum_two_more = sum_at_least_four.(3,4) # => #

When we supply more arguments to total at least four, Ruby will evaluate the function.


  sum_two_more.(5,6) # => 18
  sum_two_more.(5,6,10) # => 28

So you see, Ruby makes functional programming fun! Currying and partial function application become a lot easier to understand when you can look at it in the friendly syntax Ruby provides. You are now ready to add another tool to your toolbelt when designing your programs!

Responses