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:
- The
Map.putdeals with Elixir basic data types (map) - 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:
- It adds a level of indirection
- 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:
- Enumerable data types from elixir
- 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?
Trending in Discussions
Other Trending Topics
Chat & Discussions>Discussions
Latest on Elixir Forum
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #elixirconf-us
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #metaprogramming
- #hex
- #performance











First 6 of 6 Posts
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
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:
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:
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
is more valuable than
I would argue that
doesn’t really add enough value over
because
build_substitutionalready heavily hints in that direction (and there isn’t much other noise on that line to begin with).There is more noise in
for example it thrusts the implementation detail that the quiz contains a
:current_questionvalue in my face (which I probably should know if I work with it) - that together withselect_a_random_questionprobably hints atpick_current_question- but I have to correlate these two separate pieces of information to come to that conclusion - reasoning overhead thatdoesn’t require.
JEG2
A few quick thoughts:
Enum.mapcaseMap.putinterrupts my reading flow more than theEnum.map, possibly because it feels more low level to meEnum.mapversion we usedbuild_substitutionfor the real work (maybe it should have just beenbuild_substitutionswithEnum.mapincluded)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
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
Still, thank you everyone for your comments !
peerreynders
As a simple example: this was my version of this.