A couple weeks ago, I wrote a blog about building function-building functions in JavaScript. This week, I'm going to go through the same concept using Ruby. I'm going to stick as closely as possible to the same flow as the last article to make it easy to compare.
We all know what a programming function is and what it does. It encapsulates a particular behavior. For example, this function divides any number you'd like by 5.
def divide_by_5(number)
number / 5
end
divide_by_5(15)
# => 3
In life, we often see different variations of a complex behavior, and this is a situation we see a lot in programming as well. For example, imagine we wanted to add some complexity to the above function so that it only divides numbers that are cleanly divisible by 5. We could easily do this:
def divide_evenly_by_5(num)
if num % 5 === 0
return num / 5
end
return "#{num} is not divisible by 5!"
end
divide_evenly_by_5(15)
# => 3
divide_evenly_by_5(7)
# => "7 is not divisible by 5!"
But we might need to similarly divide by other numbers later on in our program. We could write a new function for each number, but that would be a pain. Instead, let's create a function which in turn creates other functions!
To do this, we'll convert our function into something called a lambda function, which is basically a way to encapsulate a certain behavior into a variable, very similar to const myFunction = function(arg) { console.log("hi "+ arg) }
. We need to use .call()
to call this lambda function:
divide_evenly_by_5 = ->(numerator) {
if numerator % 5 === 0
return numerator / 5
end
return "#{numerator} is not divisible by 5!"
}
divide_evenly_by_5.call(15)
# => 3
divide_evenly_by_5.call(7)
# => "7 is not divisible by 5!"
But this is too strict! Let's loosen up the lamda so that we can use whatever numerator AND denominator that we want. This is how we set up our flexibility in Ruby, rather than the wrapper function that we used in JavaScript. It's basically the same thing that we did in the JavaScript tutorial, but in reverse. (Instead of building a super-function out of 2 smaller functions, we're building a super-function and then breaking it into 2 smaller functions.) Note that the order of your arguments are important here:
divide_by = ->(divisor, numerator) {
if numerator % divisor === 0
return numerator / divisor
end
return "#{numerator} is not divisible by #{divisor}!"
}
divide_by.call(5, 15)
# => 3
divide_by.call(5, 7)
# => "7 is not divisible by 5!"
Now, we need to use a concept called currying to essentially split this method up into 2 "parts." and in Ruby we use a .curry
method to do it. This is going to represent whatever the first divide_by
argument is, which is why it's important to order them correctly:
divide_by_5 = divide_by.curry.call(5)
Now, we can call divide_by_5
and provide the second argument, in this case the numerator:
divide_by_5.call(10)
# => 2
The benefit of this pattern is that we can now assign this behavior to any divisor/number variation. As the complexity of the behavior goes up, this becomes more and more useful.
Here's the full code:
divide_by = ->(divisor, numerator) {
if numerator % divisor === 0
numerator / divisor
end
return "#{numerator} is not divisible by #{divisor}!"
}
divide_by_5 = divide_by.curry.call(5)
# =>
div_by_8 = divide_by.curry.call(8)
# =>
divide_number_by_100 = divide_by.curry.call(100)
# =>
divide_by_5.call(15)
# => 3
divide_by_5.call(8)
# => "8 is not divisible by 5!"
divide_number_by_100.call(500)
# => 5
Disclaimer: There is a LOT more to these lambdas than this application, they're super useful in Ruby coding. I'm just getting started diving into their functionality.
Top comments (0)