Functional Aspects of Ruby

Posted Jan 22, 2016 by Brandon Gafford

Introduction

If you think of an indecipherable sea of parenthesis when you hear the term “functional programming,” you’re not alone. Functional programming can seem daunting, alien, and impractical, especially coming from a solid background in an imperative or object oriented language like C or Java. You might have seen or used an implementation of LISP, a language designed almost 60 years ago, and missed the luxuries and syntax of more modern languages. The good news is that we’ve learned a lot about programming languages since 1958, and functional programming doesn’t have to be scary. In fact, if you work in Ruby regularly, you have probably used aspects of it, maybe without even knowing.

What is Functional Programming?

Before we start, let’s nail down what we mean by “functional programming.” At its core, functional programming is about organizing code around functions rather than objects. In order for this to work, functions have to be considered a first class data type in a programming language. That’s a fancy way of saying that functions can be stored into variables, returned from other functions, used as parameters, potentially even mutated, just like any other piece of data in a program. Instead of diving deep into more descriptions, let’s see how it works with some examples.

Blocks and Procs

The most ubiquitous functional aspects of Ruby are functions that iterate through lists, like “each”:

array = ["Bob", "Jane", "Joe"]
array.each do |name|
  puts name
end

If you’ve spent much time in Ruby, you’ve probably seen something like this before and intuitively understood what it was doing. It reads almost like pseudocode: “For each name in array, print that name.” What’s actually going on under the hood, though, is one of the most fundamental ideas in functional programming with a little Ruby flair. The code between the do and end is what’s referred to in Ruby as a “block”, and it represents a function literal in the same way that 3 represents an integer literal. What’s happening in the code above is that a function is being defined as a block and immediately passed as a sort of argument to the function each. In order for the block to be treated like data on its own, though, it has to be packaged up into a special Ruby class called Proc. Proc takes a block as a parameter in a similar way to each, but it allows the block to be stored and interacted with like other Ruby objects. Later on, to run the function, you invoke the method call on it. Let’s break the block out of the each above so we can see how this works more clearly.

people = ["Bob", "Jane", "Joe"]

print_arg = Proc.new do |arg|
  puts arg
end
# prints Linda to the console
print_arg.call("Linda")
# prints Bob, Jane, and Joe to the console
people.each(&print_arg)

The block has been defined explicitly as a Proc and assigned to a variable. Now it’s easier to tell that the block is just a function that is being passed into each as an argument. The & before print_arg takes the Proc object and unpacks the block out of it for each, just the opposite of what Proc.new does. With this block in hand, each then iterates through each element in the array and calls the function with that element as the argument. The cool part about this is that, since Procs are just objects, you could have any number of various Procs defined, assign them to variables, and even decide at runtime which one you’re going to use!

people = ["Bob", "Jane", "Joe"]

nice_greeting = Proc.new do |arg|
  puts "Hey #{arg}!"
end

grumpy_greeting = Proc.new do |arg|
  puts "I still need my coffee, #{arg}"
end

if Time.now.hour < 9
  greet = grumpy_greeting
else
  greet = nice_greeting
end
people.each(&greet)

At the beginning, we define two different Procs and store them into variables: nice_greeting and grumpy_greeting. The magic happens in the if statement after that: based on the time of day, one of those two Procs is going to get stored into greet. If it’s too early in the morning, grumpy_greeting is stored, otherwise nice_greeting is stored. Notice that this condition is only executed once, not once for each element in the lists. Once we have the Proc we want, we pass it into each as a parameter. When the last line gets executed in the afternoon, the function stored in nice_greeting would get executed 3 times, once for each name in people. This kind of usage of Procs gives a ton of flexibility, even for an already-flexible language like Ruby.

Functions as Composition

Imagine that you are creating a Galaga clone, and you need to design the enemy ships. One of the basic things an enemy needs to be able to do is move back and forth and shoot lasers at the player. Traditional object oriented programming would have you represent this as an Enemy class, perhaps with a position attribute, a move method, and a shoot method. It might end up looking something like this:

class Enemy
  attr_accessor :position

  def initialize(position)
    @position = position
    @direction = 1
  end

  def move
    @position[:x] += @direction
    @direction = -@direction if @position[:x] <= LEFT_BOUND or @position[:x] >= RIGHT_BOUND
  end

  def shoot
    Laser.new(@position)
  end
end

The enemies will now all simply move back and forth and shoot at the player. To make the game more challenging, some of the enemies will move diagonally instead of just back and forth. Since they share all of the functionality of the base Enemy class, except for the movement, it seems to make sense to make another class that extends Enemy. Let’s call it DiagonalEnemy:

class DiagonalEnemy < Enemy
  def initialize(position)
    super(position)
  end

  def move
    @position[:x] += @direction
    @position[:y] += @direction

    @direction = -@direction if @position[:x] >= RIGHT_BOUND or @position[:x] <= LEFT_BOUND or
                                @position[:y] <= TOP_BOUND or @position[:y] >= BOTTOM_BOUND
  end
end

It turns out that still makes the game too easy. You decide that you also want to have some “boss” ships that shoot seeking missiles instead of the normal lasers. Again, this shares all of the functionality of Enemy, except for shooting this time, so you add another class for it:

class MissileEnemy < Enemy
  def initialize(position)
    super(position)
  end

  def shoot
    Missile.new(@position)
  end
end

This is pretty decent by now; Most of the enemies will move back and forth shooting lasers, some enemies will move diagonally shooting lasers, and some enemies will move back and forth shooting missiles. You could continue adding other classes that represent specialized behavior, but at some point you’ll probably ask yourself the dreaded question “What if I want an enemy to move diagonally and shoot missiles?” Traditional class hierarchies don’t have a simple solution to this problem. Multiple inheritance gets ugly very quickly, and by now we’ve realized that it’s not a great solution. You could create a class extending just DiagonalEnemy and copy over the shoot method, or conversely extend MissileEnemy and copy over the move method. Perhaps, instead of playing favorites, you just create a new class and copy both methods over to it. Any way you try to use inheritance, you’d end up with duplicated code, meaning you have to maintain the same code in two places. This increases effort and the chance for bugs.

If you think about it, though, DiagnoalEnemy, MissileEnemy, and MissileDiagonalEnemy aren’t describing new things that are similar to Enemy, they’re describing variations on the behavior that Enemy objects have. “Behavior” sounds an awful lot like “function.” In fact, the new classes are just defining functions that change behavior. Why do we need separate classes to hold those functions? It turns out we don’t! Procs are ideally suited to describe these behaviors. Here’s what the revised version using Procs might look like:

class Enemy
  attr_accessor :position

  def initialize(position, move, shoot)
    @position = position
    @move = move
    @shoot = shoot
    @direction = 1
  end

  def move
    @position, @direction = @move.call(position, direction)
  end

  def shoot
    @shoot.call(@position)
  end
end

move_back_and_forth = Proc.new do |position, direction|
  position[:x] += direction
  direction = -direction if position[:x] <= LEFT_BOUND or position[:x] >= RIGHT_BOUND

  [position, direction]
end

move_diagonally = Proc.new do |position, direction|
  position[:x] += direction
  position[:y] += direction

  direction = -direction if position[:x] >= RIGHT_BOUND or position[:x] <= LEFT_BOUND or
                            position[:y] <= TOP_BOUND or position[:y] >= BOTTOM_BOUND

  [position, direction]
end

shoot_laser = Proc.new do |position|
  Laser.new(position)
end

shoot_missile = Proc.new do |position|
  Missile.new(position)
end

normal_enemy = Enemy.new({x: 0, y: 0}, move_back_and_forth, shoot_laser)
diagonal_enemy = Enemy.new({x: 0, y: 0}, move_diagonally, shoot_laser)
boss_enemy = Enemy.new({x: 0, y: 0}, move_back_and_forth, shoot_missile)
challenge_boss_enemy = Enemy.new({x: 0, y: 0}, move_diagonally, shoot_missile)

Now, the Enemy class defers the desired behaviors to the Procs that are passed in, simply calling them to determine what to do. These Procs are defined next, using the same logic as the previous examples, except now they don’t rely on classes and inheritance to house them. At the end are the various behaviors that are possible, showing how easy it is to reuse and combine the behaviors once they are defined. You don’t need to stop there, either. These behaviors can be reused to define even more complex behaviors. For example, if you wanted an enemy to shoot both a laser and a missile or to shoot a laser while moving, just pass Enemy a Proc that combines those basic behaviors:

shoot_both = Proc.new do |position|
  shoot_laser.call(position)
  shoot_missile.call(position)
end
two_shot_enemy = Enemy.new({x: 0, y: 0}, move_back_and_forth, shoot_both)

shoot_and_move = Proc.new do |position, direction|
  shoot_laser.call(position)
  move_back_and_forth.call(position, direction)
end
run_and_gun_enemy = Enemy.new({x: 0, y: 0}, shoot_and_move, shoot_laser)

If you decide sometime later that you want to modify what it means to shoot a laser or missile, you don’t have to modify it in every place that that does it; you just have to modify the corresponding method, and everything else will use the new method without having to worry about the change.

Summary

While functional programming can seem a bit alien at first, Ruby does an excellent job of making it feel like a natural part of the language. Once you wrap your mind around how it works, it really can open up interesting new ways of thinking about problems. We’ve only scratched the surface here; there’s a whole world of awesome functional ideas you can incorporate once you understand the basics. Object oriented programming is helpful when dealing with certain types of abstractions, but it isn’t always the best tool. Since Ruby contains both objected oriented and functional features, you can always use the best tool for a given problem.

Posted on Medium

engineering