> 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.
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 is actually very smart and helpful, thanks! :) My only gripe with autokey is that it's the only thing holding me back from wayland. Hawck is supposed to work with wayland but I never got it to work, was a while ago I tried though
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.
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.
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`
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.
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
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
> 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
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.
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).
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.
> "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.
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.
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
...
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.
48 comments
[ 4.7 ms ] story [ 102 ms ] threadIt'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.
https://github.com/elixir-editors/emacs-elixir#pairing
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)
https://github.com/snyball/Hawck
https://github.com/flatpak/xdg-desktop-portal
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`
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
if x, do: y, else: z
Or, though it's not very idiomatic Elixir, this also works:
x && y || z
> 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.
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.
[1] https://learnyousomeerlang.com/syntax-in-functions
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."
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:
Can be read as "A and B and C" while: 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
Here, "use GenServer" imports default implementation of various handlers which can be overridden.https://github.com/elixir-lang/elixir/blob/main/lib/elixir/l...
Like I would favour:
over the case version, for example, but that's just me."Or just:"
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).
another use case that has not been mentioned is generated functions
you can't generate case statements as easily