Functional Programming Techniques With Ruby: Part I

Share this article

Ruby is an interesting language, in that it supports the use of multiple paradigms. One of these is the “functional paradigm”.

Features of Functional Languages

Using a language in a functional style implies you have access to a few key features:
  • Immutable values: once a “variable” is set, it cannot be changed. In Ruby, this means you effectively have to treat variables like constants.
  • No side-effects: when passed a given value, a function must always return the same result. This goes hand in hand with having immutable values; a function can never take a value and change it, as this would be causing a side-effect that is tangential to returning a result.
  • Higher-order functions: these are functions that allow functions as arguments, or use functions as the return value. This is, arguably, one of the most critical features of any functional language.
  • Currying: enabled by higher-order functions, currying is transforming a function that takes multiple arguments into a function that takes one argument. This goes hand in hand with partial function application, which is transforming a multi-argument function into a function that takes less arguments then it did originally.
  • Recursion: looping by calling a function from within itself. When you don’t have access to mutable data, recursion is used to build up and chain data construction. This is because looping is not a functional concept, as it requires variables to be passed around to store the state of the loop at a given time.
  • Lazy-evaluation, or delayed-evaluation: delaying processing of values until the moment when it is actually needed. If, as an example, you have some code that generated list of Fibonacci numbers with lazy-evaluation enabled, this would not actually be processed and calculated until one of the values in the result was required by another function, such as puts.

Using Ruby as a Functional Language

In this series, we will visit each of these features one by one, and look at how you can use them to produce cleaner, more efficient, and better structured Ruby code. I should point out in advance a warning: using some of the techniques we will cover in production code is not recommended. I’ll try to clearly state, whenever I can, which features these are and why they are bad. The fact is, though, that Ruby is, at heart, an OO language for an OO world.  Functional style code can be written in Ruby easily, but it suffers mostly from performance problems as a result of not being the focused on the paradigm. More on that later; for now, let’s talk about the topic at hand: immutability.

Immutability, and Side-Effect Free Code

To start the series off, let’s explore immutability and side-effect free code. Pure functional languages do not allow mutable data, and as a result, functions cannot do anything but take arguments and produce a result using these arguments.

What Is Side-Effect Free Code, and Why Is It Good For Me?

Functional programming is a paradigm based mostly on the principles that programming should follow the rules of mathematics. Let’s take the following, valid imperative programming paradigm expression:
x = x + 1
If we apply the rules of maths to this, we should be able to rebalance the equation:
x - x = 1
∴ 0 = 1 (!?)
Woah, what happened there?! Clearly, this doesn’t work. The imperative style of programming doesn’t match the mathethematical underpinning of functional programming. Mathematical functions are built on the idea of immutability; that is, values cannot be redefined like we have done in the imperative statement above. We can combat this problem by using a new constant to represent the updated value:
x = 1
y = x + 1
Now this is perfectly valid in both paradigms:
y = x + 1
∴ x = y - 1
The functional programming paradigm, with its roots based in mathematics, uses this basic idea at its core. When using Ruby for functional programming techniques, and you want to try to avoid side-effects, you should endeavour to never redefine a variable; that is, treat variables as constants. There are numerous benefits to this approach, some important ones of which are:
  • Functions, or methods, become easily unit tested. This is because, if they never mutate data, but rather only ever provide copies or new objects as return values, the same input is guaranteed to always produce the same output. Thus, the code can be said to be “side-effect free”.
  • Your code becomes incredibly concise and readable. As you will have to create a new variable for every mutation you need to do, it will be obvious at first glance as to the scalability and shortfalls of a particular method. Pure functional languages often tout the easy ability to reason about performance of a given block of code as a feature.
  • Chaining becomes incredibly easy, mainly because what is returned by every function is a new, immutable value that can be passed into the next without worry, and discarded without concern.
  • Your functions will be domain free; you can apply them in an infinite number of ways, as they have no state specific logic hidden inside of them.

Writing Side-Effect Free Code

Version Number Parsing

In 2010, Yahuda Katz posted a simple, but interesting question on Twitter1 :
Question: what’s the easiest way to get [“X::Y::Z”, “X::Y”, “X”] from “X::Y::Z” in Ruby?
Of course, there are dozens of solutions to this problem, but solving it in a functional style proved to be a lure too good to resist for the Ruby Quicktips Tumblr 2:
def module_split(module_path, separator = "::")
  modules = module_path.split(separator)
  modules.length.downto(1).map { |n| modules.first(n).join(separator) }
end

module_split("W::X::Y::Z")
There are many benefits to writing this method in a functional style with zero side-effects:
  • Easily testable with unit tests.
  • Uses as many core Ruby methods as possible, decreasing the likelihood of error creep and wheel re-invention.
  • Pretty fast. Uses memoization where possible, and optimised Ruby core methods elsewhere.
  • Clean, readable, easily understood; it virtually reads like plain English.

Fluent DSLs With Chainable Methods

One major benefit of using a functional style is that it generally makes chaining a piece of cake. In this example, we’re going to try and build a DSL for building up a block of CSS code; we’ll build up a selector, along with properties and values to set styles on. As such, we need a class, with two methods: one for adding properties, and one for setting the selector.
class CssBlock
  # We'll add some methods in here.
end
First, we’ll set the selector as a mandatory property in the initialiser. We’ll also set up the properties attribute as an empty Hash:
class CssBlock
  attr_reader :selector, :properties

  def initialize(selector, properties = {})
    @selector = selector.dup.freeze
    @properties = properties.dup.freeze
  end
end
Note that we’ve used an attr_reader for both properties, and we’ve duplicated and frozen the values; now we can guarantee the passed in values cannot be modified. Next, we create the code to add some properties to the block:
class CssBlock

  # ...

  def set(key, value = nil)
    new_properties = if key.is_a?(Hash)
      key
    elsif !value.nil?
      {
        key => value
      }
    else
      raise "Either provide a Hash of values, or a key and value."
    end

    self.class.new(self.selector, self.properties.merge(new_properties))
  end

end
The key takeaways from this block of code are:
  • We accept either a Hash of properties to merge in, or a key/value pair to add to the already existing properties.
  • We return a new instance of the CssBlock class, setting the properties to be the merge result of the existing properties and our new properties.
  • We raise an error if they didn’t give us the right format of items.
  • We do not add, edit or modify any state stored on the copy of the object we’re dealing with. Once the object is instantated, that’s it; it’s fixed as it was.
Now, let’s add a method to serialise the contents of this class, which is basically us just defining the #to_s method:
class CssBlock

  # ...

  def to_s
    serialised_properties = self.properties.inject([]) do |acc, (k, v)|
      acc + ["#{k}: #{v}"]
    end

    "#{self.selector} { #{serialised_properties.join("; ") } }"
  end
end
We use #inject to build up an array of stringified properties. One absolutely crucial little detail here is that we do not modify the acc
variable within the block. As Ruby’s equivalent of the fold/foldr function found in most functional programming languages, #inject is a staple method for building up objects in a functional way as shown. Now to test it out:
CssBlock.new("#some_id .class a").set("color", "#FFF").set({ "color" => "#000", "text-decoration" => "underline"}) # => "#some_id .class a { color: #000; text-decoration: underline }"
Huzzah, it works! Not only that, but it’s super simple, easily testable, and should prove to be very easy to work with or extend as necessary. For your convenience, here’s the full source code:
class CssBlock
  attr_reader :selector, :properties

  def initialize(selector, properties = {})
    @selector = selector.dup.freeze
    @properties = properties.dup.freeze
  end

  def set(key, value = nil)
    new_properties = if key.is_a?(Hash)
        key
      elsif !value.nil?
        {
          key => value
        }
      else
        raise "Either provide a Hash of values, or a key and value."
      end

    self.class.new(self.selector, self.properties.merge(new_properties))
  end

  def to_s
    serialised_properties = self.properties.inject([]) do |acc, (k, v)|
      acc + ["#{k}: #{v}"]
    end

    "#{self.selector} { #{serialised_properties.join("; ") } }"
  end
end

Caveats of Side-Effect Free Code In Ruby

The reality of trying to write side-effect free code in Ruby is that it doesn’t really work for much else but experimentation at this stage. The raw truth of the matter is that Ruby has several major problems handling properly immutable data:
  • Freezing objects cannot be automated. You can get part way by monkey patching the #new_method on Class to call freeze on the return value of the original #new method, but that method doesn’t get called when certain core types are instantiated (Hash, String, Array, etc.). That means you’ll have to manually freeze objects if you want them to be forced immutable; if you don’t, you can’t guarantee you’re actually writing side-effect free code.
  • The steep cost of object creation in Ruby means that using a functional style of programming is going to prove costly to any performance related goals. Hopefully, this problem will fade away as Ruby implementations push for faster interpreters.
  • One of the key benefits of functional programming, simpler concurrency, is difficult to achieve in Ruby due to the presence of the GIL and a naïve threading implementation. In alternative Ruby implementations such as JRuby and Rubinius, this is not such a big problem. Although Ruby 1.9 has made big strides towards a better threading model with Fibers and such, it will still make fully exploring this realm of benefits harder than it is in a purpose built functional language as there is still no way to have proper parallel threads in CRuby.
Whilst these techniques are not necessarily useful in production code, they are extremely useful to keep in mind when designing robust object models.

Next Time…

We’ll talk about higher-order functions, and how they allow currying, a very interesting and highly useful programming approach. 1: http://twitter.com/wycats/status/9042964562 2: http://rubyquicktips.com/post/1018776470/embracing-functional-programming

Frequently Asked Questions about Functional Programming Techniques with Ruby

What are the key differences between functional programming and object-oriented programming in Ruby?

Functional programming and object-oriented programming are two different paradigms that Ruby supports. In object-oriented programming, the focus is on objects and their interactions. It’s all about encapsulating data and behavior into objects. On the other hand, functional programming emphasizes immutability, pure functions, and higher-order functions. It avoids changing-state and mutable data. The key difference lies in the way they approach problem-solving. While object-oriented programming is about creating instances of objects and manipulating them, functional programming is about creating a set of functions that map input to output to produce a result.

How does functional programming improve code readability and maintainability in Ruby?

Functional programming can significantly improve code readability and maintainability in Ruby. It encourages the use of pure functions, which have no side effects. This means that given the same input, a function will always produce the same output. This predictability makes the code easier to understand, test, and debug. Moreover, functional programming avoids mutable state, reducing the risk of bugs related to state changes. It also promotes code reusability through higher-order functions and function composition.

What are some common functional programming techniques used in Ruby?

Ruby supports several functional programming techniques. Some of the most common ones include the use of higher-order functions, function composition, and recursion. Higher-order functions are functions that can accept other functions as arguments or return them as results. Function composition is a technique where you create a new function by combining two or more functions. Recursion is a technique where a function calls itself until a base condition is met.

How can I implement immutability in Ruby?

Immutability is a core concept in functional programming. In Ruby, you can achieve immutability by using frozen objects. The ‘freeze’ method in Ruby can be used to prevent modifications to an object. Once an object is frozen, any attempt to change it will result in a RuntimeError. This can be useful in maintaining the integrity of your data and avoiding unexpected side effects.

Can I use functional programming techniques with Ruby on Rails?

Yes, you can use functional programming techniques with Ruby on Rails. While Rails is primarily an object-oriented framework, it doesn’t prevent you from using functional programming concepts. You can use higher-order functions, function composition, and immutability in your Rails applications. However, it’s important to note that using functional programming in Rails may not always be the best approach, depending on the specific requirements of your application.

What are the benefits and drawbacks of using functional programming in Ruby?

Functional programming in Ruby has several benefits. It can lead to cleaner, more readable code. It can make your code easier to test and debug. It can also help you avoid common bugs related to mutable state. However, functional programming in Ruby also has some drawbacks. Ruby is primarily an object-oriented language, and using functional programming techniques can sometimes feel a bit unnatural or forced. Additionally, functional programming can be more difficult to understand for developers who are new to the concept.

How does functional programming handle side effects in Ruby?

In functional programming, functions are expected to be pure, meaning they should not have any side effects. A side effect is any change in the system that is observable outside the called function. In Ruby, you can manage side effects by isolating them and making them explicit. This can be achieved by returning a new value instead of modifying an existing one, or by using monads, a functional programming concept that allows you to handle side effects in a controlled manner.

How can I use higher-order functions in Ruby?

Higher-order functions are a key concept in functional programming. In Ruby, you can create higher-order functions by defining methods that accept other methods as arguments or return them as results. Ruby’s Enumerable module provides several higher-order functions such as ‘map’, ‘reduce’, and ‘select’. These methods take a block of code (which is essentially a function) as an argument and apply it to each element in a collection.

What is function composition in Ruby and how can I use it?

Function composition is a technique in functional programming where you create a new function by combining two or more functions. In Ruby, you can use the ‘proc’ method to create functions and the ‘call’ method to invoke them. You can then use the ‘>>’ or ‘<<‘ operators to compose these functions. The result is a new function that applies the original functions in sequence.

How does recursion work in functional programming with Ruby?

Recursion is a technique where a function calls itself until a base condition is met. In functional programming with Ruby, you can use recursion to solve problems that require repetitive computation. A recursive function typically has two parts: a base case that defines the result for a known input, and a recursive case that breaks down complex inputs into simpler ones. However, it’s important to note that excessive recursion can lead to a stack overflow error, so it should be used judiciously.

Nathan KleynNathan Kleyn
View Author

Hi! I currently work for Intent HQ as Director of Engineering, writing functional Scala by day and Rust by night. I write for SitePoint about Ruby, and I’m on Twitter and on GitHub. You can get in touch with me at mail/at/nathankleyn.com.

functional programming
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week