On this page

  1. …in which I complain about Elixir syntax
  2. A case for QuickCheck for Elixir?

Originally posted on Medium

I kinda like Elixir. I even think that Elixir is a huge leap towards the future and all the right things that should be happening in the Erlang world (see related post here). I still get irritated by it :)

What follows is a highly opinionated … erm … opinion. I am right and you are wrong ;)

It’s not the syntax that’s important, it’s the consistency

I will be hanged out to dry for this, then brought in and hanged dry again. But here goes.

Let’s start with Erlang. Erlang has one of the most concise and consistent syntaxes in programming languages.

Erlang syntax primer

1: A sequence of expressions is separated by commas:

Var = fun_call(),
Var2 = fun_call2(), Var3 = fun_call3(Param1, Param2)

2: A pattern match after which we return a value is followed by an arrow. From a programmer’s point of view, there’s no difference if this is a pattern match introduced by the function declaration or by a case, receive or if

function_declaration(Param1, Param2) -> 
  Var = fun_call(),
  Var2 = fun_call2(), Var3 = fun_call3(Param1, Param2),
     {some, pattern} ->
         do_four() %% the value of this call will be returned

3: Choices are spearated by semicolons. Once again, there’s no real difference (at least to the programmer) between choices in function heads (we choose between different patterns) and choices in case, if or receive:

function1({a, b}) ->
function1({c, D}) ->
  case D of
    {d, e} -> do_c_d_e();
    {f, g} -> do_c_f_g()

4: There are also blocks, and a block is terminated by an end (there’s a bit of inconsistency in blocks themselves: begin...end, case of...end, try...catch...end, if...end). E.g.:

function() ->
    case X of
      b -> 
         if true -> ok end

What of Elixir?

And here’s my problem with Elixir: You have to be always be aware of things and of what goes where when.

do..end vs ->

def f(a, b) do
  case a do
    {:c, :d} ->
    {:e, :f} ->
      receive do
        {:x} -> do_smth()
        {:y} -> something_else()

What? Why is there suddenly an arrow? In a language where (almost) everything is a do...end the arrow is jarringly out of place. For the longest time ever I honestly thought that the only thing separating pattern matches in a case statement is identation. Which would be weird in a language where whitespace is insignificant.

There’s an argument that these are individual patterns within a do...end block separated by newlines and denoted by arrows (see here). cond, case and receive all use that. The argument is moot, however.

Here’s how you define an anonymous function in Elixir:

a_fun = fn x -> x end

This is an even weirder construct. It’s not a do...end. But it has an arrow and an end. Moreover, regular functions are also individual patterns within a do...end block:

defmodule M do
  def x(:x), do: :x end
  def x(:y), do: :y end

And here’s how you define similar constructs with arrows:

a_fun = fn :x -> :x
           :y -> :y

result = case some_var do
           :x -> :x
           :z -> :z

If arrow is just a syntactic sugar for do...end, why can’t it be used in other places? If it’s used for sequences of patterns, why can’t it be used for function declarations?

There’s definitely an argument for readability. Compare:

## arrows
case some_var do
  :x -> :x
  :z -> 

## do..end as everywhere
case some_var do
  :x, do: :x
  :z do

But then… Why not use the more readable arrows in other places as well? ;)

## regular do..end
defmodule M do
  def x(:x), do: :x end
  def x(:y) do

## arrows
defmodule M do
  def x(:x) -> :x
  def x(:y) ->

So, in my opinion, arrows is a very weird incosistent hardcoded syntactic sugar. And it irritates me :)

Moving on

Parentheses are optional. Except they aren’t

One of the arguably useful features of Elixir is that function calls do not require parantheses:

> String.upcase(“a”)
> String.upcase “a”

Except that if you want to keep your sanity instead of juggling things in your mind, you’d better off using the parentheses anyway.

First, consider Elixir’s amazing pipe operator:

## instead of writing this
Enum.map(List.flatten([1, [2], 3]), fn x -> x * 2 end)

## you can write this
[1, [2], 3] |> List.flatten |> Enum.map(fn x -> x * 2 end)

Whatever is on the left side of the pipe will be passed as the first argument to the whatever’s on the right. Then the result of that will be passed as the first argument to the next thing. And so on.

However. Notice the example above, taken straight from the docs. List.flatten, a function, is written without parentheses. Enum.map, also a function, requires parentheses. And here’s why:

> [1, [2], 3] |> List.flatten |> Enum.map fn x -> x * 2 end
iex:14: warning: you are piping into a function call without parentheses, which may be ambiguous. Please wrap the function you are piping into in parentheses. For example:foo 1 |> bar 2 |> baz 3Should be written as:foo(1) |> bar(2) |> baz(3)

This is just a warning, and things can get ambiguous. That’s why you’d better always use parentheses with pipes. It gets worse though.

Remember anonymous functions we talked about earlier? Well. Let me show an example.

Here’s how a function named flatten declared in module List behaves:

> List.flatten([1, [2], 3])
[1, 2, 3]
> List.flatten([1, [2], 3])
[1, 2, 3]
> [1, [2], 3] |> List.flatten
[1, 2, 3]

Here’s how an anonymous function behaves:

> a_fun = fn x -> List.flatten x end
#Function<6.54118792/1 in :erl_eval.expr/5>> a_fun([1, [2], 3])
** (CompileError) iex:17: undefined function a_fun/1> a_fun [1, [2], 3]
** (CompileError) iex:17: undefined function a_fun/1iex(17)> [1, [2], 3] |> a_fun
** (CompileError) iex:17: undefined function a_fun/1
 (elixir) expanding macro: Kernel.|>/2
 iex:17: (file)

Wait. What?!

Oh. Right. Anonymous functions, what a nice joke. Let’s all have a good laugh. An anonymous function must always be called with .()

> a_fun.([1, [2], 3])
[1, 2, 3]
> [1, [2], 3] |> a_fun.()
[1, 2, 3]

Because parentheses are optional, right. And functions are first class citizens, and there’s no difference if it’s a function or a variable holding a function, right? (In the example below &List.flatten/1 is equivalent to Erlang’s fun lists:flatten/1. It’s called a capture operator.)

> a_fun = List.flatten
** (UndefinedFunctionError) undefined function List.flatten/0
 (elixir) List.flatten()> a_fun = &List.flatten
** (CompileError) iex:19: invalid args for &, expected an expression in the format of &Mod.fun/arity, &local/arity or a capture containing at least one argument as &1, got: List.flatten()> a_fun = &List.flatten/1
&List.flatten/1> a_fun([1, [2], 3])
** (CompileError) iex:21: undefined function a_fun/1> a_fun.([1, [2], 3])
[1, 2, 3]

Yup. Because parentheses are optional.

If you think it’s not a big problem, and that anonymous or “captured” functions are not that common… They may not be common, but they are still used. An empty project using Phoenix:

Every time you use a “captured” or an anonymous functions, you need to keep the utterly entirely useless information: they are invoked differently than the rest of the functions, they require parentheses etc. etc.

> 1 |> &(&1 * 2).()
** (ArgumentError) cannot pipe 1 into &(&1 * 2.()), can only pipe into local calls foo(), remote calls Foo.bar() or anonymous functions calls foo.()> 1 |> (&(&1 * 2)).()

Local. Remote. Anonymous. Riiiight

Compare with Erlang:

1> lists:flatten([1, [2], 3]).
[1,2,3]2> F = fun lists:flatten/1.
3> F([1, [2], 3]).

Before we move on. Calls without parentheses lead to the unfortunate situation when you have no idea what you’re doing: assigning a variable value or the result of a function call.

some_var = x  ## is it x or x(). Let's hope the IDE tells you

And yes, this also happens.

do..end vs ,do:

This has been explained to me here. I will still mention it, because it’s annoying the hell out of me and is definitely confusing to beginners. Basically, it’s one more thing to keep track of:

def oneliner(params), do: resultdef multiliner(params) do one two result end

Yup. A comma and a colon in one case. No comma and no colon in the second case. Because syntactic sugar and reasons.

Originally published on Medium

Recently I got into a shouting match about QuickCheck implementation for Elixir.

My original reaction:

Why? Quckcheck is for companies only. Elixir is used by zero companies (except maybe 2 startups)

My highly non-scientific view of Erlang/Elixir world is something like this (don’t get offended by circle sizes, I’m bad at Venn diagrams):

If there is a company that sponsored the development of QuickCheck for Elixir, it’s great (both for the company and Quviq). However, it offers almost zero value to the rest of us for obvious reasons:

So that’s the main problem I have with this announcement and presentation: you’ve whetted our appetites. Now what?

What I would really love to see instead is just a clear licensing mechanism. Let’s say something like this:

Then the “Testing concurrency with Elixir” presentations will be “oh, cool, I can use it” instead of “meh, I can’t use it, because I don’t even know how much it costs or whether I can afford it”.

Hence my reaction ☺ But it’s me, and I’m always bitter

Oh. By the way. If you think that I’m spreading some imagined hate about Elixir… go and take a cold shower.