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?

First 6 of 6 Posts Switch mode

0x6a68

0x6a68

As it happens, i am currently also reading Designing Elixir Systems with OTP and was wondering about this abstraction too.

Thanks for bringing it up! I hope someone is able to enlighten us :slight_smile:

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 #2
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.

Fl4m3Ph03n1x

Fl4m3Ph03n1x OP

Thank you everyone for your responses.
I understand this is all about telling a good story. I guess this is what I am afraid of.

For example, I really like Tolkien (the auhtor) yet my partner doesn’t really like his stories. Some do, but prefer the cinema version which is widely different from the original (see The Hobbit book VS movies). My point is, everyone has a different idea of what a good story feels like. I have no doubt that experience plays a role when deciding to use this principle, it’s the subjective part I am afraid of :smiley:

Still, thank you everyone for your comments !

peerreynders

peerreynders

As a simple example: this was my version of this.

— All posts loaded —

Where Next?

Trending in Discussions Top

AstonJ
As the title says, please share what you’ve been up to with Elixir. Whether that’s been learning it, looking into it, making stuff with i...
2977 91561 914
New
byu
@chrismccord : I just saw the Extract AGENTS.md from Phoenix.new into phx.new generator commit to the phoenix project. My initial shotgu...
New
arcanemachine
I was working on an Ecto migration and I needed a timestamp. So, for the nth time, I looked up the different data types for timestamps, a...
New
AstonJ
Just a general thread to post chat/news/info relating to AI/ML stuff that may be relevant for Nx now or in the future. Got anything to sh...
New
type1fool
I just stumbled on a newly redesigned elixir-lang.org. :tada: It looks like @Software_Mansion did the work, and I think it is generally a...
New
juhalehtonen
There has been a thread to discuss the Stack Overflow Developer Survey on this forum every year since 2018, so here’s yet another one for...
New
alexslade
Fly’s CEO posted this recently - Turn And Face The Strange · The Fly Blog It says that Fly is going all-in on sprites, which is a worry ...
New

Other Trending Topics Top

JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge & Solve. They are GUI (Emerge) and State management (S...
New
ausimian
Emily is an Elixir library that runs Nx computations on Apple’s MLX. Install it as the default Nx backend and Nx, defn, Axon, Nx.Serving,...
New
akoutmos
@hugobarauna and I (Alex Koutmos) have been hard at work on writing a book on Nerves that takes you from simply blinking LEDs to building...
New
wintermeyer
There are three potential reasons for members of this forum to have a look at https://vutuv.de You are tired or annoyed of LinkedIn. Yo...
New

We're in Beta

About us Mission Statement