Rewriting Conditional Statements In Pure Object Oriented Programming
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.
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
andfalse
, 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: