Unpacking Elixir: Syntax
underjord.io
underjord.io
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.
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.
I'd love if electric-pair-mode could do pairing with words, not only brackets.
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 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.
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`
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."
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.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.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...
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."Or just:"
def zero?(n), do: n == 0If 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).
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
...another use case that has not been mentioned is generated functions
you can't generate case statements as easily
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.Eg. myvar = 5 myvar = if somepredicate() do 200 end
Will currently assign None to myvar if somepredicate is false.
myvar = if somepredicate() do 200 else myvar end
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 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
endif x, do: y, else: z
Or, though it's not very idiomatic Elixir, this also works:
x && y || z
var = if x, do: 200, else: 5 def eat_value([first_value, | _rest_of_list]) do> 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.