Procs, Modules, and Mixins: Ruby’s Benchwarmers

Enrique Pittaluga
Analytics Vidhya
Published in
10 min readMay 21, 2020

--

Introduction

In many cases, basic OOP education focuses on data types, variables, methods, arrays and hashes, and classes. There are plenty of good reasons to start with the basics and to become comfortable with these tools before delving deeper into less navigated, turbulent waters. Indeed, some of the oldest human wisdom praises simplicity. However upon achieving a certain level of comfort with the basics, a programmer should look forward in order to continue growing. Consequently, the basics become building blocks which allow programmers to engage with other, elaborately designed libraries and frameworks. Generally, the better that a person understands the basics of any topic, the easier new and complicated topics will be to understand. Certainly the purpose of this article is not to propose that novice programmers should learn basic skills any less; to the contrary they are an indispensable part of programming.

However, in and of themselves, in their most basic forms, they do not cover all of the situations that software engineers face in the field — err, on the computer screen. So if the basics are so good but they do not provide enough functionality to cover every single design implementation, what pray tell is a coder to do? Its funny you should ask! There are some use cases that demand more flexibility when creating objects, functions, and classes within software applications. In this article, I would like to explore a few of OOPs shortcomings and afterwards provide a broad and basic introduction into some tools that Ruby gives us which augment the default functionality of the most basic building blocks of OOP: Procs, Modules and Mixins.

Let’s explore some of the disadvantages of OOP

Often times Object Oriented Programming is pitched as an ostensible paragon of problem-solving power and engineering might. However, before OOP, there were other paradigms used in the computer science field, such as Procedural or Functional programming, for example. In fact, there may be a set of problems where procedural programming is more efficient for solving than object-oriented programming. So if there are at least two schools of thought about how to program and the mere existence of both of them suggests that they were developed to solve a particular set of problems, it stands to reason that at least some trade offs exist between them, and by extension disadvantages to OOP.

The short comings with OOP are conceptual, stylistic, and architectural. It is no secret that Object Oriented Programming can be confusing and consequently have a steep learning curve. It requires time to get used to how to solve problems from this approach since how objects are created and other details may not seem logical at first. Additionally, object oriented design can be verbose. It astounds me the amount of packages and files and directories that a convention-fitting program needs to have to do relatively simple tasks. Another way to think of this is overhead. Programs will frequently become bigger than we, as programmers, originally intended. Consequently, program efficiency and performance can suffer due to its own size and architecture. Even, inheritance — one of OOPs defining features — has its shortcomings with certain edge cases where a member of a Class family has or lacks certain attributes common to its own hierarchy, but that it shares with another, unrelated class.

Fortunately, there are strategies and features that can be used to reduce the sheer size of the program, reduce the repetition of boilerplate code, and containerize component logic. Sure these details may seem trivial when beginning the never-ending journey that is programming. Even more cynically, these concerns may seem pompous in that they are simply a ruse to show off, using more complex syntax than necessary; all the while hiding clear meaning when a problem could be solved with a little extra work in accessible and clear code. But many lessons can be learned along the time it takes to tread the yellow brick road, if only to unmask a phony wizard in the end.

Procs

A proc is an object which encapsulates a block. Simple enough, class dismissed. JK. It is worth spending a moment more on this statement in order to appreciate its power. Firstly, literal blocks of code — the code between do and end or in between { and } — were not reusable nor pass-able, until now. So through encapsulation, we can abstract away some repetition and use the block-level scope to our advantage. Thus, procs increase the modularity of code and reduce repetition. Ironically, Ruby documentation touts procs as “an essential concept in Ruby and a core of its functional programming features”. Perhaps defining it as a core of the functional programming features of Ruby sheds some light as to how procs can solve some of OOPs shortcomings.

Procs can be created a number of ways, but in this article we will focus on the Proc class constructor syntax. This code is similar to other constructors however note the use of curly braces which produce a Proc data type, as proven below:

x = Proc.new {}puts x.inspect # => #<Proc:0x00007ff5a5900950@proc.rb:3>

Ok, so that is fine and dandy. But how do we use them? Well, one way to use procs is to substitute small, repetitive blocks of code. For example, if you have an object in a program and find yourself having to iterate through the object in order to do a repetitive task, you could use a proc to make your code more D-R-Y:

x = Proc.new {|num| puts num * 4}[1, 2, 3, 4, 5, 6, 7].each &x # => 4, 8, 12, 16, 20, 24, 28

In this case, our repetitive task was multiplying the number in the array by 4. Note the use of the ampersand, ‘&’, which lets Ruby know that ‘x’ is not any ole’ variable, it is a special one. Here is another example below:

x = Proc.new { |word| word.length > 6 && word.length % 2 == 0 }long_words = ['apple', 'wisdom', 'anonymous', 'elementary', 'monitor', 'computer', 'available', 'independence']puts long_words.select &x # => elementary, computer, independence

In the previous example, the proc is used to substitute the block expected by the .each method. Furthermore, you can also pass procs as first-class citizens into methods and invoke them using their .call method:

x = Proc.new do |word|      word.capitalize!      word.reverse!      puts word * 4   enddef some_method(proc_x)   proc_x.call("hip hip")endsome_method(x) # => pih piHpih piHpih piHpih piH

Additionally, you can create a custom method with custom block functionality, as detailed below:

x = Proc.new { puts "This is Proc-tically impossible"}def some_method   yieldendsome_method(&x) # => This is Proc-tically impossible

And if you are feelings dangerous, procs can even be used to short-circuit some data type conversions and iterator logic. According to the Ruby documentation, this is because “any object that implements the .to_proc method can be converted into a proc by the ‘&’ operator, and therefore can be consumed by iterators.” Of Ruby’s core classes, Symbols, Methods and Hashes implement the .to_proc method.

arr = ["1", "2", "3"]puts arr.map &:to_i #=> 1, 2, 3
arr = ["Danger's", "my", "middle", "name"]puts arr.map &:upcase #=> DANGER'S, MY, MIDDLE, NAME

Procs can be used to simplify block-level code and utilize variables with a local scope. Even more importantly, procs are Ruby’s implementation of Closures which allow them to remember the context in which they were created. But thats a topic for another day. Instead of having to repeat code multiple times, the block itself can be passed around the program, since the proc is functionally an object. As such, we can assign the code to variables as we have shown and reuse, transform, and insert the code as needed. In my opinion, this is definitely a worthwhile tool to become familiar with and keep in the tool kit.

Modules and Mixins

Modules are a great way to provide additional state and behavior to classes without having to worry about the inheritance chain and overwriting other methods. They provide access to methods and constants defined within the module declaration. Modules provide support for both instance-level methods and module-level methods but classes which include, or use, behavior from modules only have access to the instance-level methods. Conversely, module-level methods can be accessed directly on the Module object.

While the benefits of the Object Oriented inheritance are well documented, it should also be noted that there are some limits owing to the verticality of the inheritance chain. Indeed, classes inherit from their ancestor classes and replicate functionality in their children classes, but have no native way to share state and behavior with unrelated classes. On the surface, it may seem illogical to share properties among unrelated classes but this is actually quite common in real life. Imagine dogs and cats can cause allergies, but so too can flowers and grass, and peanuts and wheat. Wrapping behavior in this way and sharing it among disparate classes is a common use for modules.

Let’s try this out containerizing some module methods in coding examples. First, lets make a parent class:

# superhero.rbclass Superhero   def righteous_call(evil_doer)      puts "Hey, put that back, #{evil_doer}!"   endend

Great, we landed on superheroes. Next, lets outfit some of them and make a Stark Industries Module that only some superheroes will be able to use:

# stark.rbmodule Stark   def thrusters      puts "Boomm!!"   end   def ai_sunglasses(name)      puts "A.I. System will track #{name} through sunglasses..."   endend

Now, it is time to make those superheroes and show how a module can provide access to behavior outside of class-based inheritance.

# hero.rbrequire_relative "./superhero"
require_relative "./stark"

class Spiderman < Superhero
include Starkendclass Ironman < Superhero include Starkendspidey = Spiderman.new iron = Ironman.newspidey.thrusters # => Boomm!!iron.thrusters # => Boomm!!spidey.ai_sunglasses("Wilson Fisk") # => A.I. System will track Wilson Fisk through sunglasses...iron.ai_sunglasses("Victor Von Doom") # => A.I. System will track Victor Von Doom through sunglasses...

Unlike the class inheritance syntax, Class < ParentClass, modules are imported within the body of the class using the include keyword. I like to think that this makes sense because the Class has not become something new, nor has it received new attributes; instead it has adopted new behaviors through the module import.

Even though Spiderman and Ironman are both superheroes, not all superheroes that we create may have access to Stark Industries thrusters and A.I. Sunglasses. Batman, for example, may be a superhero, but he is from the DC universe so he would not have access to Tony Stark’s goodies. Modules become a handy way to share behavior across classes in order to overcome the restrictions of inheritance.

This feature can be taken even further through the use of Mixins. A mixin is simply adding access to multiple modules within a Class. Since Ruby does not support multiple inheritance, we can use multiple Modules to mix into -read Mixin- a subclass. Functionally mixins provide the extensibility and modularity that Multiple inheritance affords other languages. Therefore, while Ruby does not provide all the OOP features that other languages implement, mixins achieve this functionality and are a classic example of ducktyping within Ruby. According to the authors, Harmes and Diaz, ducktyping comes from the expression, “if it walks like a duck and quacks like a duck, then it must be a duck.” In the context of programming, it can be interpreted to mean that if a given object defines and functions like another object, then it must be the second object as well.

Another use case for modules is creating namespaces for common Class names, avoiding conflicts in a program. Imagine a program of heroes and villains. If the program was big enough, it is reasonable to assume that there could be conflicts from colliding Class names. Note the example below:

First lets create a Superhero Module:

module Superhero   class Cyclops      def eye         puts "My eye(s) shoots lasers. Zap!"      end   end   class Titans      def original_team         puts "Robin, Superboy, Kid Flash, Wonder Girl"      end      def teen_titans         puts "Nightwing, Raven, Starfire, Beast Boy"      end   endend

Next, lets make a Greek Module:

module Greek   class Cyclops      def eye         puts "I traded my eye to see the future"      end   end   class Titans      def original_team         puts "Oceanus, Tethys, Hyperion, Theia, Coeus, Phoebe, Cronus, Rhea, Mnemosyne, Themis, Crius and Iapetus"      end   endend

Now, it time to prove that by using modules both versions of the Titans and Cyclops classes can exist in the same Ruby file, without having any namespacing errors.

require_relative './superhero'
require_relative './greek'
s_cyclops = Superhero::Cyclops.newg_cyclops = Greek::Cyclops.news_titans = Superhero::Titans.newg_titans = Greek::Titans.news_cyclops.eye # => My eye(s) shoots lasers. Zap!g_cyclops.eye # => I traded my eye to see the futures_titans.original_team # => Robin, Superboy, Kid Flash, Wonder Girlg_titans.original_team # => Oceanus, Tethys, Hyperion, Theia, Coeus, Phoebe, Cronus, Rhea, Mnemosyne, Themis, Crius and Iapetus

Between inheritance and modules, Ruby provides software engineers with the tools to solve most any Class problem. Inheritance is one of the principle tenets of OOP but despite the power and versatility it provides, it does not solve all design challenges. As they relate to this challenging set of edge cases, modules provide modularity and increased flexibility while reducing repetition. Despite the fact that modules are not instantiable, they do provide a namespace for organizing, defining, and structuring code cleanly. A simple way to tell apart when to use class-based inheritance and mix-in modules is by defining the relationship. Spiderman “is a” superhero; while he only “has a” Stark Industries Thruster and AI Sunglasses.

Conclusion

Ruby provides programmers with all of the basic functionality expected in an Object Oriented Language. Then, Ruby adds flexibility, extensibility, and increased modularity through some of its advanced features, like Procs, Modules, and Mixins. These feature refine the usage of Ruby by providing additional tools which augment a programmers capacity to adhere to design ideals.

Bibliography

--

--