dogweather

dogweather

Idiomatic, real-world Elixir resources?

Can anyone recommend books/courses/videos that use real-world Elixir? E.g.:

  • Idiomatic error handling design, whether it’s {ok/error, ...} or something else.
  • Uses specs as they “ought” to be, however that is.
  • Uses the various mechanisms for creating types such as @type, defstruct, and @enforce_keys.
  • Test-first coding generally, if that’s common.
  • Idiomatic module and function naming.
  • Use of keyword lists in function signatures.

(The resource or two I’ve purchased teach good architecture, apparently, but are not idiomatic, unfortunately.)

Most Liked

dimitarvp

dimitarvp

This is definitely partially against my principles because I don’t believe in universal truths most of the time. But I’ve allowed myself to compile a list of those possible idiomatic good practices – which IMO go well beyond Elixir itself.

  • Use @spec. However I’ll strongly recommend against that during rapid prototyping. It will just get in the way. The moment you find a module haven’t changed in 2 weeks however, go put specs on its functions and start chasing the green output of dialyzer.

  • Complement @spec with your own guards. If you know a datatype you use is actually a list, DO NOT guard it with is_list(param). Define a guard and use that instead:

@spec orders :: [%Order{}]

defguard is_orders(x) when is_list(x)

def eligible_for_discount?(orders) when is_orders(orders) do
  #...
end
  • Do not overdo docs. Many times they should even be superfluous. If your function is well-named and the parameter names hint at their purposes clearly enough you should be just fine by putting something like Checks if this batch of orders is eligible for a corporate discount or even without docs at all. Module docs are mostly important for widely used libraries. For your own open-source pursuits and commercial projects they are mostly a distraction. Go for GitHub’s wiki articles, or Atlassian’s Confluence pages, etc.

  • Don’t use import if you can help it. Resist it with all your might. Only use it if you have no other choice and your code will not work otherwise. If you really must use it, use the :only option.

  • Don’t overdo case and cond. Use function heads with hardcoded parameter values, say def length([]), do: 0 for example. Readability often means less coding lines (though certainly not always; too much brevity can make your code very cryptic but that’s a bigger topic I won’t tackle here).

  • Use macros and don’t be shy about it – but use them sparingly. For example when you want to transcode stuff and writing all functions manually would be tedious: that is a very valid usage. Macros should be used to make an otherwise annoying code generation easier and quicker. Some here in this forum get tempted to use them to introduce their own extra syntax in Elixir but I am very against this; these efforts usually attempt to emulate another language’s constructs and rarely seem aimed at actual productivity (at least not in the way I understand it; I realise this is a controversial opinion). They are usually used to rekindle nostalgia for paradigms the author fondly remembers from other languages. I am personally strongly opposed to these temptations; if using macros can both reduce the coding lines and improve readability then I’ll use them. But I am not a fan of the LISP approach where people make their own DSLs which are perfectly suited for the project… and nobody else except the author can maintain them or the project after they are introduced in it. We have to keep our work maintainable. That’s the professional courtesy I want to show to future maintainers of my code and thus I resist using macros in Elixir beyond simple readability improvement and reducing otherwise tedious coding work.

  • GenServer, Agent and many OTP primitives and derivatives should be used for (a) keeping state and (b) making your system resilient to partial failures. If you find yourself using them for other purposes then you are abusing them and should reconsider your approach.

  • As noted above, I like @enforce_keys for structs, provided I am not in the rapid prototyping phase. I will prefer compile-time errors to runtime errors 99% of the time.

  • Some extraneous code is acceptable if it makes using your modules more ergonomic and intuitive. For example I regularly make wrapper Config modules for my apps and libraries where I expose both generic put / get functions and specific put_* and get_* functions (say, put_timeout or get_batch_maximum). I’ve had colleagues argue with me over this and I’ll concede this is a personal preference but to me the intuitive reading of my code almost as if it’s English should win over small extra amounts of code.


I am probably missing several others but hopefully this gives you an idea of what I find to be good practices. Apologies for this becoming rather huge.

sasajuric

sasajuric

Author of Elixir In Action

I agree with app vs library point, but I’m not sold on the conclusion. I don’t have enough of a sample to know what “the wast majority of applications” does, but in my limited experience of trying both approaches for a couple of years, I definitely prefer app code with specs, because they help me understand what are the allowed inputs and what are the possible outputs, which in turn reduces the uncertainty when reasoning about the code.

Consider for example the following function:

def create_account(params)

What is this function accepting and what is it returning? To know this we need to look into the implementation. If we’re lucky, the impl is simple and we see everything in that function’s body. But quite often, we have to dig through a bunch of other private functions to piece it all together. This is cumbersome, energy draining, and error prone.

Consider in contrast the spec version:

@type account_params :: %{
        first_name: String.t(),
        last_name: String.t(),
        email: String.t(),
        password: String.t(),
        role: Role.t()
      }

@spec create_account(account_params()) :: {:ok, Account.t()} | {:error, Ecto.Changeset.t()}
def create_account(params) do

This already tells us so much more without forcing us to read the entire implementation. That is in my view a key benefit of typespecs. Combined with proper naming and well defined responsibilities they provide great help in reasoning about the code, library or app.

This is why I prefer to say that types are first and foremost about documenting, not about catching bugs. When a type checker complains, it means that there is a discrepancy between the spec and the implementation. From the standpoint of the desired program behaviour we don’t know what is wrong (in fact the program might even be correct), but we do know that the documentation (spec) doesn’t match the code, so we need to fix something to sync them.

I agree though that one should not be fanatical. I typically spec only API functions (i.e. exported funs which are not marked with @impl or @doc false), except for modules such as Phoenix controllers or Graphql resolvers, because specs don’t really help here at all. I might occasionally spec a private function if I estimate that it’s interface is complex and the inclusion of specs will assist the reader.

When it comes to docs/moduledocs, I don’t write them in the app code unless there are some important implications not obvious from the name/spec combo.

lucaong

lucaong

My suggestion, if you feel you know Elixir well, but you want to step up your practical Elixir coding skills, would be to read the codebase of some quality open-source projects. The source code of Elixir core library is a great place to start, as well as delving into libraries that you use, and possibly contributing to them.

Subjective opinion here: I would not stress too much about what is idiomatic or not, but rather develop a sense for which codebases are easier to navigate and why. Reading other people’s code helps in this respect, because we always approach it as “beginners” with little context. As developers, I think we focus too much on code style on a superficial level (stuff that linters can enforce) and too little on deeper aspects of what increases/decreases cognitive load.

Where Next?

Popular in Chat/Questions Top

ariandanim
Hello all, I am still learning Elixir, then go into Phoenix, i am try search in google but find the programming phoenix 1.4, another for...
New
pdgonzalez872
Do we have a list of academic/research papers: about Elixir/Erlang? that use Elixir/Erlang? about the Beam? If so, could you please po...
New
LegitStack
I’m not a hugely experienced programmer, just a few years. So I’m looking for resources to learn about a topic but I can’t seem to find m...
New
KSingh1980
How to run the random query and get the result in JSON format and send it as a response. The query will be in the following form DB Name...
New
OmanF
In the attached screenshot (taken from an exercise in “Programming Elixir”) can be shown that BugReport is defined as a struct that, itse...
New
Scoty
Hey, I am currently reading Programming Elixir and I am doing one of the exercises where you should write a solution to the “I’m thinkin...
New
Fl4m3Ph03n1x
Background Hello all! So after my controversial introduction with Learning Elixir, frst impressions ( plz don't kill me ! ) - #39 by easc...
New
jslearner
Will learning Erlang really help in being a better Phoenix or Elixir developer or is it a waste of time?
New
Nopp
Hello there, i have a lot to read and to learn, but are there some books, that are focussing on how i “should” plan the architecture of ...
New
Besto
I’ve been trying to start learning Elixir for a couple of weeks while I develop a tiny project I have on node.js, but every time I try to...
New

Other popular topics Top

senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New
aesmail
Hello guys, I have finally made it. I created an admin interface for a framework. It’s been on my todo list for years and with the curre...
New
vegabook
I’m brand new to Phoenix and I have stripped one of the demo applications to the bone. I just want to get an svg up on the screen. Here i...
New
RisingFromAshes
I’ve read in another post that it may be possible with a router helper - but I couldn’t find an appropriate one, and tbh, I’m still just ...
New
jason.o
In the code below, if the create action is not set to accept “extra_key” as an input, it errors out with a message shown above. Is there ...
New
rms.mrcs
Hi, I need to transform a list of numbers into a map where the keys are the indexes and the values are the original values of the list. ...
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
sergio
Kind of like when jquery came out, it was super necessary. Existing drag and drop libraries have a bunch of baggage to support old browse...
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
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