Fl4m3Ph03n1x

Fl4m3Ph03n1x

Applying Single Level of Abstraction

Background

I am reading Designing Elixir Systems with OTP where they introduce the concept of Single Level of Abstraction.

Code

This concept looks rather bizarre and abstract but with an example it becomes easier to understand. Using the examples in the book, imagine we have a starting code like the following:

def select_question(quiz), do: 
  quiz
  |> Map.put( :current_question, select_a_random_question(quiz) ) 
  |> move_template(:used)
  |> reset_template_cycle

This code is not horribly hard to read, but we are dealing with 2 levels of abstraction here:

  1. The Map.put deals with Elixir basic data types (map)
  2. The rest of the code affects an abstraction called question.

So based on this principle, the authors proposed a new version of the code:

def select_question(quiz), do:
    quiz
    |> pick_current_question()
    |> move_template(:used)
    |> reset_template_cycle()

defp pick_current_question(quiz), do:
  Map.put(quiz, :current_question, select_a_random_question(quiz))

My issues

This principle looks nice, but I have a couple of issues with it:

  1. It adds a level of indirection
  2. When is enough enough? When do you know when to use it?

To better explain, I will give another example from the book:

  def new(%Template{} = template), do:
    template.generators
    |> Enum.map(&build_substitution/1)
    |> evaluate(template)

This piece of code also deals with 2 abstractions:

  1. Enumerable data types from elixir
  2. The concept of a template

So, according to this rule, the code could be improved:

  def new(%Template{} = template), do:
    template.generators
    |> generate_substitutions()
    |> evaluate(template)

  defp generate_substitutions(%{substitution: _sub} = generator), do:
    Enum.map(&build_substitution/1)

However the authors choose not to do it.

Question

I wonder if they decided the level of indirection was not worth it. If so, how and when do you decide the indirection is worth it or not? What criteria do you usually use?

Most Liked

peerreynders

peerreynders

I don’t see this as having anything to do with introducing abstractions but with capturing and communicating intent.

Three months after writing the code:

Scenario 1:

Map.put( :current_question, select_a_random_question(quiz) ) 

I have to mentally parse this Elixir code in order to discover what it is doing and then divine from the context in which the code exist why it is doing it - and the why is the important bit when reading code.

Scenario 2:

pick_current_question()

Oh, I want to pick the current question - done.

Now I may have a moment of self doubt and go read the function’s definition to verify how this was actually accomplished but then I can blissfully forget about the implementation details. To me personally

pick_current_question()

is more valuable than

Map.put( :current_question, select_a_random_question(quiz) )

I would argue that

generate_substitutions()

doesn’t really add enough value over

Enum.map(&build_substitution/1)

because build_substitution already heavily hints in that direction (and there isn’t much other noise on that line to begin with).

There is more noise in

Map.put( :current_question, select_a_random_question(quiz) ) 

for example it thrusts the implementation detail that the quiz contains a :current_question value in my face (which I probably should know if I work with it) - that together with select_a_random_question probably hints at pick_current_question - but I have to correlate these two separate pieces of information to come to that conclusion - reasoning overhead that

pick_current_question()

doesn’t require.

17
Post #3
JEG2

JEG2

Author of Designing Elixir Systems with OTP

A few quick thoughts:

  • First, we didn’t invent this idea and there’s prior art to consult: Single Level of Abstraction (SLA) [Principles Wiki]
  • I liked your argument about us missing the Enum.map case
  • Like everything, it’s a tradeoff you should use when it helps
  • I do find the Map.put interrupts my reading flow more than the Enum.map, possibly because it feels more low level to me
  • Note that even in the Enum.map version we used build_substitution for the real work (maybe it should have just been build_substitutions with Enum.map included)
  • I really like how @peerreynders described the value of this technique: for me it’s about getting the function to tell a good story
djantea

djantea

This is something that I recently red about how the single level of abstraction principle can help to better structure the code, and make it easier to follow: https://aaronrenner.io/2019/09/18/application-layering-a-pattern-for-extensible-elixir-application-design.html

Hope this helps.

Where Next?

Popular in Discussions Top

blackode
Elixir Upgrading is so Simple in Ubuntu and It worked for me Ubuntu 16.04 git clone https://github.com/elixir-lang/elixir.git cd elixir...
New
cvkmohan
The upcoming Phoenix 1.6 release looks very interesting. Became a habit to watch the commits - and - what they are bringing in. phx.gen...
New
owaisqayum
I have a sample string sentence = "Hello, world ... 123 *** ^%&*())^% %%:>" From this string, I want to only keep the integers, ...
New
Nvim
Elixir appears to be a superior language to Python. I don’t see any advantage of Python over Elixir. Are there any?
New
MarioFlach
Hello, I want to share a project I’ve been working on for a while: https://github.com/almightycouch/gitgud Background Some time ago I ...
New
Fl4m3Ph03n1x
Background This question comes mainly from my ignorance. Today is Black Friday, one of my favorite days of the year to buy books. One boo...
New
WildYorkies
It seems that the more I read, the more I find Elixir users speaking about all the ways that Elixir is not good for x, y, and z use cases...
New
AstonJ
Please see the new poll here: Which code editor or IDE do you use? (Poll) (2022 Edition) It’s been a while since we first asked this, I...
208 31142 143
New
100phlecs
Are there any downsides, like perf issues, to putting all functional components in CoreComponents as long as you prefix it with the conte...
New
pdgonzalez872
If this has been asked here before, please point me to where it was asked as I didn’t find it when I searched the forum. Maybe a mailing ...
New

Other popular topics Top

mcarvalho
What is the difference between System.get_env and Application.get_env? For example, what are best practices to use one versus another.
New
lessless
I believe there are people here who are dealing with CSV files import on the daily basis, and since Excel is a really popular tool there ...
New
johnnyicon
Hi all, I’ve just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I’m trying to use Postgres...
New
JakeBecker
TL;DR: I’ve just released an implementation of Microsoft’s IDE-independent Language Server Protocol for Elixir. It adds language support ...
1144 53690 245
New
josevalim
Hi everyone, One of the features added to Elixir early on to help integration with Erlang code was the idea of overridable function defi...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
klo
Got a question about when to concat vs. prepending items to list then reversing to achieve appending. So i know lists boil down to [1 | ...
New
axelson
This post is a wiki (feel free to hit the edit button near the bottom right of this post to add your own changes!) This post collects co...
239 47930 226
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New

We're in Beta

About us Mission Statement