Rewriting Conditional Statements In Pure Object Oriented Programming

Gabriel
Committed Engineers — by Per Angusta
7 min readApr 8, 2020

My name is Gabriel and I am a committed engineer at Per Angusta. I like to play with different languages and different paradigms, and Ruby is a big sandbox that offers a lot of freedom to do things in different ways.

The Ruby language purpose is happiness and smoothness, so Matz made Ruby a multi paradigm language, i.e. you can do Procedural Programming (PP), some Functional Programming (FP) and Object-Oriented Programming (OOP). This melting pot is a two-edged sword, but where Ruby certainly shines is in its true nature: OOP.

Photo by Arek Socha on pixabay

For whatever reason, the first paradigm we intuitively choose to solve problems is often the procedural one, e.g. “if this then do that, else do that”.

Here I am not saying that OOP is always the better way to solve problems (because sometimes it’s not), and I am not suggesting that we should forget the other paradigms in Ruby. I just want to remind that pure OOP does not need specific syntaxes for control structures like if/else (see control structures in the Smalltalk programming language), and I want to play with OOP design to emulate some basic procedural structures in Ruby.

Spoiler: This article is mainly inspired by a part of the enlightened talk “Nothing is Something” by Sandi Metz.

Good old classic procedural conditions

Conditions in Ruby are typically written like that:

How to get along without this specific structure? Let’s begin to analyze it…

Well here in Ruby, whatever the expression is, it always returns an object to the if keyword. Then, if evaluates arbitrarily this object as “truthy” or “falsy”, following a simple rule: only false and nil (the singleton objects of FalseClass and NilClass) are “falsy”, and absolutely all other objects are considered “truthy”.

Then, if this object is “truthy”, the first clause is executed, otherwise it’s the second one.

Let’s turn on our rubyist imagination 😊

The entry point to think about that with an OOP mind is the “arbitrary evaluation” (we also could say “evaluation from the outside”) of the object returned by the expression. With the if keyword, this logic escapes from the OOP developer and lies deep inside the language’s hard coded mechanisms. We can leave aside this specific syntax and handle this logic with the deep nature of OOP by simply send messages to objects and let each of them choose which clause has to be executed.

More clearly, a message is a method called on an object (the receiver of the message) with or without arguments.

Let’s imagining a message that we can send to any type of object, and that contains everything required here. For example a question about itself (“Are you truthy?”), and one or two clauses. Each object would be responsible for determining which clause has to be executed.

Here are some ways we can implement this “new syntax” of conditional if/else statements, using only pure OOP messages (receiver + method + arguments):

With a block:

object.truthy? { puts "Yes, I'm truthy!" }

But because we cannot pass several blocks to methods in Ruby, we are not able to handle two different clauses this way.

With proc/lambda:

# using one single clause
object.truthy? then_do: -> { puts "Yes, I'm truthy!" }
# using two different clauses
object.truthy? then_do: -> { puts "Yes, I'm truthy!" },
else_do: -> { puts "No, I'm falsy!" }

For a better readability, we choose to not support the block argument version in order to be able to handle multiple clauses the same way.

Playing with Ruby

Before implementing our #truthy? method, we can determine what kind of objects we need to test:

Object.new # Object instance
'string' # String instance
true # TrueClass singleton
false # FalseClass singleton
nil # NilClass singleton

At first, an Object instance because all object’s classes inherit from Object (except for BasicObject or classes that explicitly inherit from BasicObject). Then a string as an example of a specialized object. Finally true, false and nil, each as singleton (i.e. the only instance) of its class.

Let’s see how our “good old procedural method” handle them with a simple iteration:

That returns:

Object instance:
with if/else keywords: truthy
String instance:
with if/else keywords: truthy
true:
with if/else keywords: truthy
false:
with if/else keywords: falsy
nil:
with if/else keywords: falsy

OK. I guess nobody is amazed :D, that’s just the basic truthy/falsy Ruby’s rules.

Implementing our brand new idea

The idea is that all objects should respond to a new truthy? method as follows:

object.truthy? then_do: -> { puts "Yes, I'm truthy!" }
# or
object.truthy? then_do: -> { puts "Yes, I'm truthy!" },
else_do: -> { puts "No, I'm falsy!" }

Two preliminary remarks:

  • Firstly, as all objects must be “truthy” by default, except for nil and false, we have to implement two different behaviors.
  • Secondly, we want to embed behaviors in existing classes. A good practice is to write them in modules to be included in those classes.

So two behaviors means two modules. For that purpose, we need to create a Truthy module for all objects, and a Falsy module for nil and false singleton objects.

It is surprisingly easy to write the Truthy module, as we just have to define a #truthy? method that takes 2 procs (or lambdas, and a lambda is a type of proc, so let’s talk about proc hereafter) captured as parameters named then_do and else_do. But as we are in the context of truthy objects, the only one responsibility of this method is to call the then_do proc. Finally, we also have to ensure that the else_do proc is optional.

Now we just have to include this module in Object class to bring the new behavior to all objects.

Object.include Truthy

Four lines of significant code :). We can test it.

That returns:

Object instance:
with if/else keywords: truthy
with #truthy? method: truthy
String instance:
with if/else keywords: truthy
with #truthy? method: truthy
true:
with if/else keywords: truthy
with #truthy? method: truthy
false:
with if/else keywords: falsy
with #truthy? method: truthy # wrong!
nil:
with if/else keywords: falsy
with #truthy? method: truthy # wrong!

Ahem… obviously, when all objects behave the same, it is not very useful…

So let’s implement a specific version of #truthy? method in a Falsy module intended to falsy objects.

It’s almost the same method as the Truthy#truthy? method, except that we have to call the optional else_do proc if present (hence our usage of the safe navigator &).

Now we include Falsy in FalseClass and NilClass to overwrite this behavior for both false and nil objects.

FalseClass.include Falsy
NilClass .include Falsy

Now let’s test again:

Object instance:
with if/else keywords: truthy
with #truthy? method: truthy
String instance:
with if/else keywords: truthy
with #truthy? method: truthy
true:
with if/else keywords: truthy
with #truthy? method: truthy
false:
with if/else keywords: falsy
with #truthy? method: falsy
nil:
with if/else keywords: falsy
with #truthy? method: falsy

We did it! We just reimplement conditional statements in pure OOP with just 9 very simple lines of code. (Okay, and 4 extra lines with the end keyword… I heard a pythonist giggling from here! :D)

Wrapping up

This specific implementation has no proper use in Ruby, but it’s a demonstration of what you can do in OOP, i.e. ask different kinds of objects in the same way (be able to send the same message to all of them, which is an application of Duck Typing), and thus put some responsibilities out of the developer’s view.

A well known application of that is the creation of an instance of a GuestUser class for example in a Rails application, which would be set to current_user in case of absence of a registered User instance. This GuestUser instance would be able to respond to a #name method (answering e.g. “guest user”), and to some permission requests (#admin? -> false), etc., allowing you to avoid repeating the famous condition “if current_user” everywhere, ’cause hey, there is always a user behind :-).

Addendum

As FalseClass and NilClass inherit also from Object, maybe you’re wondering why the Falsy#truthy? method takes priority over the Truthy#truthy? one for false and nil objects? It’s due to the way Ruby dives through classes and modules to find methods when messages are received by an object. This is where the OOP design leans! We can check this order easily with the Module#ancestors method in our favorite Ruby’s REPL (as irb or pry) as follows.

classes/modules priority order for strings: (as truthy object example)

String.ancestors
# => [String, Comparable, Object, Truthy, Kernel, BasicObject]

classes/modules priority order for false or nil:

FalseClass.ancestors # or NilClass.ancestors
# => [NilClass, Falsy, Object, Truthy, Kernel, BasicObject]

Here a graph to illustrate this logic:

Full implementation + play + output

Interested in joining an enthusiastic engineering team?
We are recruiting talented people! Checkout our open positions here:

--

--