Tell don’t ask with Ruby and Elixir

Erin Boeger
6 min readOct 16, 2020
Kids jumping outside
Credit: pixabay.com

Imagine you’re the principal of an elementary school, it is recess time and all the kids are outside making noise. Suddenly the clock strikes 1 pm and all the kids need to get to their classes.

As a principal you could ask each and every child what their name is, what grade they are in, and then tell them which class they need to go to. This will take a long time and is not very efficient.

Kids in a line
Credit: pixabay.com

Another choice is to have each child understand when they hear the recess bell they should go to class and, more importantly, each child should have enough information to carry out that task. This seems much more efficient and is how I remember school.

The first scenario where you iterate over the children one by one asking them questions, and then determine where they should go, does not sound like anything a reasonable principle would do. However, we as developers do this in our code all the time.

Take for example this scenario. You have a double nested array with important information at various indexes. You could iterate over every nested array and ask it a variety of questions and then, based on those answers, build a response.

Ruby inquisitive iterations

Ruby code example

Elixir inquisitive iterations

Elixir code sample

First off there are some slight differences due to languages. I would admit Ruby has this great ability to be very compact at times. Elixir does not have a ternary operator for example, and since it is functional, data immutability prevents changes in states, where OO does not have that restriction. However, we want to talk about how we can remove all the ‘if’ statements and be more direct.

Given edge cases and potential for empty fields we need a way to ascertain a lot of information. The overall design is also in need of help. The current implementation does not lend itself well to a directive solution thus we need to abstract.

Ruby FinalResult after abstraction

Ruby code example

Elixir FinalResult after abstraction

Elixir code sample

Here too we can see how compact Ruby can be. Given the ‘tell don’t ask’ principle it looks like we no longer need to ask all those questions at the point of iteration. Both languages have allowed us to be more direct and tell the records where to go and they know what information to return. This is done by moving that logic into a Record class.

Ruby Record class after abstraction

Ruby code example

Elixir Record class after abstraction

Elixir code sample

Wait a minute though… if you look closely at the Ruby Record class it still has ‘if’ statements, where Elixir is able to use pattern matching, guards, and a happy path to basically eliminate all ‘if’ statements. With Ruby and OO we have improved readability, yet in a way we kicked the can down the road in a few scenarios.

For some of the scenarios, with Ruby and OO, we can employ a few tricks to help us work around edge cases. The amount method is a good example.

Ruby code example

Here we can call the ‘to_s’ method, if this value happens to be nil then it returns an empty string, if the value is a string, then it returns itself. This can help us with a nil edge case. The methods following the ‘to_s’ are string safe methods so very unlikely they will error. This style of syntax is questionably readable and does the job of removing ‘if’ questions, however lacks a certain satisfaction.

With Elixir we can use a happy path and pattern matching to achieve more readable and concise code with the same method.

Elixir code sample

For those not familiar with Elixir pattern matching and guards, the first argument is pattern matching the 8th element in a list. The ‘when is_binary(float)’ is a guard that matches when the field is a string.

The happy path starts at ‘with’ and the value before the ‘<-’ arrow is the expected result, anything that does not match this value will fall into the ‘else’ clause and all values with an underscore prefix are ignored, thus anything not matching will return a ‘0’. Happy paths are awesome!

The second ‘amount_subunits’ method, with the same name, is a catch all for arguments that do not match the first method with the same name. Arguments that do not match are ignored and it returns a ‘0’ for all that make it here.

Foot kicking
Credit: pixabay.com

Back to kicking the can down the road.

There were certain methods and scenarios in Ruby that were really hard to find a way around not using an ‘if’ and yet maintain readability. Obviously we could use a ‘case’ statement, but that is semantics and does not really help us comply with the ‘tell don’t ask’ goal we are trying to achieve here. For example the ‘bank_account_number’ method

Ruby code example
kicking the can

I was able to create a helper method for ‘nil’ and empty string scenarios with ‘empty?’, but still need to check for those with an ‘if’. The good news is the abstracted Record class handles this, yet again not fully satisfied with this solution. Really tried looking at different scenarios to help remove this conditional style from the remaining methods and again struggled to find something that was reasonably easy to read and yet satisfied the ‘tell don’t ask’ directive.

Is not fully satisfying the ‘tell don’t ask’ philosophy a big deal?

Honestly everything has trade offs. I personally think using ‘tell don’t ask’ makes code more readable, extendable, and better organized. The Elixir code, in particular, is easier to read and understand because it is clearly stating what information we need to be successful and filters out what we don’t need. The Elixir code structure lends itself to big wins in many areas like logging and error detection.

With Ruby, given the state of the code after the refactor, again there is better readability and we can encapsulate the conditionals into classes who can maintain the responsibility. There may be more solutions that can satisfy the ‘tell don’t ask’ directive better and if you know please share.

In the end the ‘tell don’t ask’ coding paradigm does pay off in benefits and is a very good design pattern to use when writing your code. It works for almost all languages and programming styles. There are also speed enhancements due to how CPU’s handle predictive branching etc. Are the speed improvements significant? that is for another day lol. Hope the readability, extendibility, and code organization alone were worth your time.

--

--