48 comments

[ 4.7 ms ] story [ 102 ms ] thread
> It is fair to note that end has a lot more characters than } but I do think it comes across as more human.

It's also easier to type on non-QWERTY keyboard layouts (including the portuguese layout), where you need the Alt Gr key (the right alt key) to type curly braces.

Had not thought about it. But I and my nordic keyboards agree.
Speaking of, anyone know of a simple Emacs mode to automatically add "end" when I write "do" in elixir-mode? Ideally something language-agnostic. I know smartparens can do that, but I want something smaller and more focused.

I'd love if electric-pair-mode could do pairing with words, not only brackets.

I use Ctrl+Alt as a substitute for Alt Gr making it feel as natural as typing any other shift-bound special character on the right side of keyboard

It's standard on windows but I have to resort to a hacky solution to make it work on linux unfortunately (still don't have a nice way of doing it cleanly so suggestions are welcome, currently it's an autokey script where e.g. ctrl+alt+0 prints '}', which isn't elegant, doesn't work in wayland etc)

I use autokey not as a hotkey but as text expansion. If I type "..c" then this is expanded into "{}" with the cursor inside the braces. And to surround text with "{}" I use ",,c", hence "whatever,,c" becomes "{whatever}". I have similar text expansion for other symbols that require AltGr, square braces are ..s and ,,s for instance. On Windows Autohotkey can do the same, just the scripting language is different. The Autokey text for curly braces is simply "{}<left>" and for surrounding with curly braces the script is: import time keyboard.send_keys("<shift>+<ctrl>+<left>") time.sleep(0.25) sample = clipboard.get_selection() keyboard.send_key("<delete>") keyboard.send_keys("{%s}" % sample) Maybe it helps you.
That's the sort of reason that made me switch to US Qwerty a couple of years ago and as a programmer I should have done that from the very beginning instead of staying on Azerty BE. The shortcuts of most programs have been thought for Qwerty (thinking about you Vim). It made my developer's life much easier.

I am on macOS and I had to install Karabiner and goku (https://github.com/yqrashawn/GokuRakuJoudo) to get the french accented letters. Now right-alt+e gives me é, right-shift-alt+e gives me è, etc.

BTW, I love Elixir.

Are you aware of the "EurKEY" layout (https://eurkey.steffen.bruentjen.eu)? As a european, I think a US ansi keyboard with eurkey is the best option available, if you want to stick with something "standard". On linux it should even be available by default, although Gnome hides it behind some gsettings option. For macos there seems to be some installation involved: https://github.com/jonasdiemer/EurKEY-Mac.
It looks very interesting. Might give it a try even if I am now used to my key combinations to get the accented letters.
A friend of mine switched on mac os recently and as a french dude had trouble making accents. Do you know you can keep the vowel pressed on the keyboard to have accent suggestions like on mobile ? And mac native of course. It changed his way of writing. Hope it helps you.
the lsp (or maybe it's my editor) will also automatically do a closing `end` for every `do` similar to autobraces where typing an open `{` will automatically add a closing `}`

it means that most of the time i don't think about `end` unless it's an anonymous function `fn x -> x end`, which is where the only place i ever forget to add `end`

Probably no small part to Jose Valim speaking Portuguese
Good run-through! Just a small issue, the trailing `Map.put/3` in `add_defaults/1` is missing a key and value.
I wish that elixir had some syntax to simplify variable rebinding when returning from an if statement (as opposed to an if else). For example, some way to say leave the variable unchanged if the if statement is false instead of having to clutter up the program with one line else clauses.

Eg. myvar = 5 myvar = if somepredicate() do 200 end

Will currently assign None to myvar if somepredicate is false.

myvar = 5

myvar = if somepredicate() do 200 else myvar end

The wish of the OP is to avoid writing "else myvar". You could write a macro I guess. Something like the one below. Not that I have named the macro cond_assign for conditional assignment but there is no real assignement in Elixir, it is the match operator. Here we restrict its usage to "assignment" so expr1 should be a variable.

  defmodule OurMacro do
    defmacro cond_assign(expr1, expr2, do: block) do
      quote do
        unquote(expr1) = if unquote(expr2), do: unquote(block), else: unquote(expr1)
      end
    end
  end

  iex> import OurMacro
  OurMacro
  iex> myvar = 5
  5
  iex> cond_assign myvar, false, do: 200
  5
  iex> myvar
  5
  iex> cond_assign myvar, true, do: 200
  200
  iex> myvar
  200
Here is a better version that checks if expr1 is a variable. The condition "is_atom(x)" might be too strong.

  defmodule OurMacro do
    defmacro cond_assign(expr1, expr2, do: block) do
      case expr1 do
        {x, _, y} when is_atom(x) and is_atom(y) -> nil
        _ -> :erlang.error(RuntimeError.exception("The lvalue is not a variable: #{Macro.to_string(expr1)}"))
      end
      quote do
        unquote(expr1) = if unquote(expr2), do: unquote(block), else: unquote(expr1)
      end
    end
  end
There are a couple of options to simplify and emulate a ternary operator:

if x, do: y, else: z

Or, though it's not very idiomatic Elixir, this also works:

x && y || z

The binary matching is relatively idiomatic in liveview - check the CoreComponents files.
if you're writing Elixir code you'll find that most of the times the assignment is close to where you need the variable, so you usually end up writing something along the line of

   var = if x, do: 200, else: 5
I prefer explicit readability. Alternatives I can think of are unappealing.
Spelling/grammar:

> Elixir is functional language

It's A functional language.

> before-hand

beforehand

> They don’t deal with failure or anything like that but in functional life there are many cases where this just becomes much more readable

I would rewrite that entire sentence to "They don’t deal with failures or anything like that but in a functional life there are many cases where this becomes more readable"

> that don’t feel like normal Elixir code. Odds are you are

Should not be a period I think, but a comma.

You must’ve understood what the author meant before correcting, right?
This would probably be best sent directly to the author since they provide their email address at the bottom of the article with an explicit call for feedback.
Great article! The phrase "true and false are both atoms" confused me, as I didn't know what atoms were, but I'm not quite sure how that information could be included earlier without feeling jarring.

Also, "Not that there is only one type of pipe |> and it feeds the output into the first argument of the next function." Did you mean "Note that"?

Either way, thanks for sharing! I'm excited to learn more about Elixir.

Having dabbled a bit in Elixir during the last year, I've been wondering why function "overloads" are a thing in Elixir, since they (as the author points out) seem to be equivalent to `case` expressions. Do polymorphic functions provide any tangible benefits?
I wouldn't really call them overloads, but you're right they are a lot like case statements. The benefit is that you can combine them with pattern matching to vastly simplify how you handle edge cases. You can pull nil and error handling out of your main logic and into function heads. You can easily separate logic for different input types and values. You can do all sorts of cool stuff with it.
I believe they literally compile down to case statements.
Hi Lars! Of course you're right[1], but function heads can handle more complicated patterns than case statements, and compile down to a more complicated set of cases. So you can think of function heads/guards as an abstraction over case statements (if you squint a little).

[1] https://learnyousomeerlang.com/syntax-in-functions

To my knowledge, that's not true (at least for Elixir), and that's not what your source says, either. So long as you wrap your case arguments into a tuple, you can have the same level of complexity in either.
They are equally powerful, however the multi head approach is more convenient. You can for example have a macro inject a bunch of functions, and override just one specific head, which becomes unwieldy and significantly less maintainable with just case.
The unsatisfying answer is that elixir inherits this from erlang. I don't have a good answer on why erlang does it though.

For functions of different arities, erlang (and therefor elixir) considers them completely different functions, and requires a declaration for each arity at least.

But then elixir adds optional args which confuses the answer a little. Being able to hard code handling of exceptional constants in a separate body is very useful but like you said it's conceptually the same thing as a pattern match, and not really an answer about why that feature is there.

I've looked a bit before but I haven't come up with anything more compelling than "erlang was kinda off doing its own thing, syntax-wise, and so some of it is just Like That now."

Elixir doesn't technically have default arguments, and this isn't such a pedantic distinction since what it has is a macro that defines a whole other function.

    defmodule Foo do
      def bar(baz \\ "baz") do
        baz
      end
    end
becomes

    defmodule Foo do
      def bar do
        "baz"
      end

      def bar(baz) do
        baz
      end
    end
Which, as you pointed out, are two completely separate functions.
Ahh that makes so much sense, I feel like I should have guessed something like that was going on. Thanks!
> "erlang was kinda off doing its own thing, syntax-wise, and so some of it is just Like That now."

The syntax makes a lot more sense if you go into the history (well-documented) and understand that Erlang's initial implementation was in Prolog and a lot of the syntax can be seen as a consequence of that.

As for pattern matching in the function signature instead of forcing dropping to a case expression, it removes a level of useless and noisy indentation and, again, is a lot more like Prolog.

Even the use of , and ; as separators in Erlang make sense given that heritage. In Prolog a sequence like this:

  A, B, C.
Can be read as "A and B and C" while:

  A; B; C.
Can be read as "A or B or C". So function bodies using , as a separator and ; between alternatives with . as a terminal follows straight from Prolog.
I find it quite readable especially with nested logic. Here is an example:

https://github.com/elixir-ecto/ecto/blob/master/lib/ecto/que...

Edit: Just remembered another use case where function overload is used to override default implementation

  defmodule Downloader do
    use GenServer

    def handle_cast(:start, state) do
      ...
    end

    def handle_cast(:stop, state) do
     ...
    end
  end
Here, "use GenServer" imports default implementation of various handlers which can be overridden.

https://github.com/elixir-lang/elixir/blob/main/lib/elixir/l...

It is the same function with different clauses using pattern matching. It is a powerful way of handling different cases. There is no overload at all. The concept does not exist in the language. And if the arity differs, then they are completely different functions even if the name is the same.
They are useful for recursion:

    def factorial(0), do: 1
    def factorial(n) when n > 0, do: n * factorial(n - 1)
Otherwise it comes down to a matter of taste. Sometimes they read nicer than a `case`, sometimes a `case` is better.

Like I would favour:

    def zero?(0), do: true
    def zero?(_), do: false
over the case version, for example, but that's just me.
...and, just to be "that guy" to myself before anyone else does over my contrived example:

"Or just:"

    def zero?(n), do: n == 0
There's only one situaton where I prefer a single function with a nested case: when there is a datatype guard check for an argument that applies to all invocations (precondition). This belongs on the function, then the case is pure discrimination within that type.

If multiple function heads were used, you might have to repeat the typecheck guard every time, which is ugly in code, but probably not inefficient (the Erlang pattern/guard compiler is insanely good).

In Phoenix core_components.ex, you have an interesting way of dealing with common code and also keeping the valuable pattern matching with functions. This way of handling things may not be applicable to every situation.

  def input(%{field: %Phoenix.HTML.FormField{} = field} = assigns) do
    assigns
    |> assign(field: nil, id: assigns.id || field.id) # <-- ALLOWS TO AVOID RE-ENTERING THIS FUNCTION CLAUSE
    |> assign(:errors, Enum.map(field.errors, &translate_error(&1)))
    |> assign_new(:name, fn -> if assigns.multiple, do: field.name <> "[]", else: field.name end)
    |> assign_new(:value, fn -> field.value end)
    |> input() # <-- RECURSE WHICH ALLOWS TO CHOOSE THE RIGHT FUNCTION CLAUSE BELOW
  end

  def input(%{type: "checkbox", value: value} = assigns) do
    ...
  end

  def input(%{type: "select"} = assigns) do
    ...
  end

  ...
they both compile to a case expression in Abstract Erlang Format

another use case that has not been mentioned is generated functions

you can't generate case statements as easily

The big advantage is that it saves two levels of indentation and a lot of repeated pattern matching.

  def equal?(x,y) when x == y do 
    true
  end
  def equal?(_,_), do: false
would be equivalent to

  def equal?(x,y) do
     case {x,y} do
       {x,y} when x == y -> 
         true
       _ -> 
       false
     end
  end
It's also handy in places like GenServers, where from a reading perspective clustering the hooks and their helper functions together is far more readable than the alternative.

   def handle_call(:msg1,_from,_state) do
      helper1()
      helper2()
      etc...
   end
   
   def helper1(), do: 1
   def helper2(), do: 2

   def handle_call(:msg2,_from,_state), do: :err
Is a lot better than

   def handle_call(msg,_from,_state) do
      case {msg, _from, _state} do 
         {:msg1,_from,_state} -> 
                  helper1()
                  helper2()
                  etc...
         {:msg2,_from,_state} -> :err
      end
    end
    def helper1(), do: 1
    def helper2(), do: 2
Especially remembering that you'll likely have a lot more than 2 message callbacks.
Comma after first_value?

  def eat_value([first_value, | _rest_of_list]) do