The Functional Side of Ruby

David Yu
5 min readJun 18, 2020

--

Anyone who’s taken a computer science course will know about “object-oriented programming”—a magical paradigm where code, like real-life, is composed of objects that have properties, do things, and talk to each other. But if so many modern languages are object-oriented, what isn’t?

Source: https://kingslanduniversity.com/wp-content/uploads/2019/04/functional_oo.png

The term “object-oriented” originated in the 1960s, well before the big-names in OO programming we know today. When FORTRAN on punch cards was still the gold standard, programmers had already begun to divide themselves across the line between the two main paradigms that we still use today.

We know Ruby objects quite well — a Class defines a prototype for an object, giving each object of that class a certain set of features and functions. Everything from the most basic Array to the most complex ApplicationController is treated as an object in Ruby: they have methods unique to them and belong to classes hidden deep within the Ruby source code.

Well, then, what isn’t an object?

The answer: Code blocks. Ruby has a very nice syntax for this—two, actually—you may know them as:

{|item| item.method}

-or-

do |item| item.method end

Why aren’t these objects, you might add? Well, they can’t be saved to variables, and they don’t know anything about themselves.

More specifically, they are “visitors” from another programming paradigm: functional programming.

Just like how object-oriented programming deals with objects, functional programming deals with….you guessed it, functions!

A function, as defined in math, is an operation that takes in inputs and spits them out as outputs. For example:

f(x, y) = x + y

X and Y are the inputs to the function, and the sum of X and Y are the outputs.

Likewise, a code block in ruby behaves similarly:

{|x, y| x + y}

It takes two inputs listed between the pipe (|) symbols, and outputs the return value of the rest of the code between the brackets.

So far, we’ve seen this notation used in ruby’s enumerable syntax:

array.map{|n| n ** 2}

Essentially, what .map is doing here is receiving a code block, and applying the function that the code block describes to each one of the array values. This code block would be equivalent to g(n) = n². It is a function of one input, hence why there is only one value between the pipes.

What is the difference between functions and methods, you might ask?

At the high level of Ruby and other modern programming languages, there are not that many, and they don’t matter too much.

Two big ones are:

  1. Methods belong to objects, while functions don’t belong to anyone — e.g. each object is created with a set of methods specific to that class of object, while functions can be defined and used anywhere.
  2. An object performs a method, while functions transform their inputs — e.g. a method represents an action that an object can take, while a function takes things in and spits things out without needing an actor.

How is this useful outside of enumerables? Well, there are some pretty cool things you can do with functions.

Ruby, being an object-oriented programming language, can actually turn functions into objects. Ruby has a built-in class called Proc (short for procedure) which you can create as follows:

sum_procedure = Proc.new {|x, y| x + y}#=> <#Proc:0x342ad809da78>

This allows you to “save” your code block into an object and do all sorts of things with it in Ruby.

Let’s try to use our new function:

sum_procedure(2, 3) #=> error

Why does this give us an error? Because in Ruby, you can pass arguments only to a method. In this case, sum_procedure is a Proc object, not a method, so Ruby will not know what to do. If we actually want to call the function stored within sum_procedure, we can do the following:

sum_procedure.call(2, 3) #=> 5

Why does this work? Proc comes with a handy method called #call, which applies the function stored within Proc to the arguments supplied to #call. In this case, ruby knows that (2, 3) corresponds to |x, y| in the function, and it is able to evaluate the function. Note the syntax of the statement:

object.method(arguments)

The Proc is the object, “.call” is the method, and the arguments are passed to the function. Note how there are no other objects involved than the Proc itself — this is what makes it different from a method. While methods must be called on an object, a proc provides a method #call which can be run on itself.

This seems overly complicated. What does this help us accomplish?

Here’s where things get interesting. Remember how you could create a Proc using Proc.new? Lets put that into a method:

def make_power_procedure(power)     Proc.new {|n| n ** power}end#=> <#Proc:0x342ad809da78>

When we call this method, it will return a new Proc object that contains a new function. What this function is will depend on what we pass in power. In a way, we now have code that is writing code for us! Codeception!!!

Let’s give it a spin:

square = make_power_procedure(2)square.call(3) #=> 9

See how our method just created a function?

Let’s try it again:

cube = make_power_procedure(3)cube.call(3) #=> 27

Crazy, right?

As a matter of fact, we’ve just reached the core of functional programming—the idea that functions can be created, changed, and even called on other functions (😮). Being able to create custom functions without needing to define them each time opens up a lot of possibilities for programming.

In theory, the ability to generate and process functions using Procs means that Ruby is technically a fully-fledged functional programming language. If you really wanted to, you could code a fully functional routine the same way you would in Lisp, Scheme, and Racket.

We’ll stop here, though, because Ruby was designed as a fundamentally object-oriented language, and any further function witchcraft would defeat its original purpose. While things like Lambda calculus, recursion, and mutators are cool, we would be remiss not to take advantage of Ruby’s powerful object-oriented capabilities. However, it’s nice to see that Ruby is truly a do-it-all language, and helpful to understand what exactly enumerables such as #map are doing.

Plus, truly functional programming can get pretty ugly (hint: there’s no variables, cuz variables are objects too 😜). Happy coding!

Source: https://i.redd.it/1xhq5hecs06z.png

--

--