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.

sasajuric

sasajuric

Author of Elixir In Action

I’ve been writing typspecs in exported API functions in my work projects for the past 6 years, and I absolutely recommend this practice. When I started mentoring my current client’s team, typspecs were one of the first practice I introduced. They are currently mandatory, and we enforce them via credo.

I believe this is a good practice for improving code clarity. Imagine how it would look if Elixir or Erlang docs didn’t have typespecs. The same thing holds in our own code. I find it much easier to grasp and reason about a function when there are specs. In fact, in some cases, just looking at the specs can uncover some possible code design issue.

Since we write specs, we also use dialyzer during CI to detect possible problems. It’s far from perfect, but it’s better than nothing.

I’m not a fan of using guards in addition to specs. If an API function is accepting a list, I’ll use a typespec to indicate this, not a guard. Since Elixir is dynamic, and dialyzer can’t detect all problems, this means that the function might still end up accepting a non-list, in which case it’s behaviour is undefined. I’m mostly not worried about it though, b/c such issue can be prevented with good programming practices (like sanitizing the input at the edge of the system), and in my experience such situations are rare and they mostly occur in local dev, not in prod.

I quite like norm, but I’m currently not convinced that it’s a substitute for typespecs, because it’s a runtime check. It can complement specs though by helping us introduce tighter constraints at runtime. I still didn’t use it in practice but it’s definitely on my todo list.

sorentwo

sorentwo

Oban Core Team

There are two versions of “real-world” code, in my experience. One is applications (in the end product sense of the word) and the other is libraries/packages. I think that the vast majority of applications don’t use type specs, but most widely used libraries do.

Where Next?

Popular in Chat/Questions Top

William
I would like to know that is there any online source for learning Phoenix Framework for building E-Commerce Store? Any advantage on build...
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
svetarosemond
I’m planning on purchasing Elixir in action second edition, and I was wondering if anyone could tell me based off the first edition, does...
New
InkFlo
Hi everyone, This year I’m graduated from Bachelor Degree (in computer science) from France (not really a bachelor, the exact term is “L...
New
Fl4m3Ph03n1x
Background I am trying to recycle myself and improve my knowledge about Phoenix. With 1.7 now out, this seems like a good opportunity. W...
New
ericdouglas
I think that would be really interesting to have official books created by the community about all kinds of development we can do with El...
New
wallyfoo
Long story short, I have a real world need to create a view for hooking up a shipping terminal to live data, but because of some early ar...
New
shansiddiqui94
Hello, I have an interview coming up and I seem to have forgotten important concepts of Elixir. So I was wondering if you guys know of a...
New
phykos
In Ruby, which is very similar to Elixir I do this: def test yield end test do puts("sup there") end Here, the yield keyword will be...
New
AstonJ
It’s been a while since we asked this - I’m sure others (especially newcomers to the language) will be interested to hear how you’ve all ...
New

Other popular topics Top

shahryarjb
Hello, I get Persian date from my client and convert it to normal calendar like this: def jalali_string_to_miladi_english_number(persi...
New
sorentwo
Hello! tl;dr Announcing Oban, an Ecto based job processing library with a focus on reliability and historical observability. After spen...
977 41022 311
New
ycv005
I have followed this StackOverflow post to install the specific version of Erlang. And When I am running mix ecto.setup then getting fol...
New
malloryerik
Hi, this is for people who, like me, have had some friction using .html.heex templates in VSCode. The solution seems to be, in a hyphena...
New
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New
fireproofsocks
I’m working on defining a simple Ecto schema for a table (in PostGres), but I don’t see where I can define a column as NOT NULL. Conside...
New
chrismccord
This release brings a number of exciting features, including integration with the new Phoenix LiveDashboard and Phoenix LiveView. There h...
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
WestKeys
Currently suffering from paralysis by [HTTP client] analysis. This is rather unusual in Elixirland as there tends to be consensus on the ...
New
belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New

We're in Beta

About us Mission Statement