How to "expect(…).to" in Elixir

Written . Tagged Elixir.

When I first came across ESpec, I was perplexed by syntax like

1
expect(pet).to be("Cat")

That doesn’t look like Elixir!

It is in fact a controversial feature of Erlang, that may be removed in future versions of Elixir. So you probably shouldn’t use it, but it can still be interesting to know how it works.

ESpec’s expect function returns a tuple like {Expect, pet}, containing a module name and the argument. So we effectively have

1
{Expect, pet}.to be("Cat")

This, in turn, is interpreted as an Expect.to function call, with the tuple itself as the last argument:

1
Expect.to(be("Cat"), {Expect, pet})

This applies to any number of arguments. If we had done

1
{Expect, 1, 2, 3}.to(4, 5, 6)

then it would be interpreted as

1
Expect.to(4, 5, 6, {Expect, 1, 2, 3})

and so on.

How it all fits together

This is how you might implement a minimal version of expect(…).to:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
defmodule Expect do
  def to({:be, value}, {Expect, value}) do
    IO.puts("Hooray, they're both '#{value}'!")
  end

  def to({:be, expected}, {Expect, actual}) do
    IO.puts("Nay! Expected '#{expected}' but got '#{actual}' :(")
  end
end

defmodule Example do
  def run do
    expect("Cat").to be("Cat")
    expect("Cat").to be("Dog")
  end

  defp expect(actual) do
    {Expect, actual}
  end

  defp be(expected) do
    {:be, expected}
  end
end

Example.run

Output:

Hooray, they're both 'Cat'!
Nay! Expected 'Dog' but got 'Cat' :(

If you want another example of how this might be used, see my ExMachina.with sketch.

What is this syntax?

This is Erlang tuple modules.

They are controversial in Erlang and Elixir both, and José Valim wants them gone in Elixir 2.0. Problems include hard-to-read stacktraces, slower dispatch, and that they can encourage writing code in an object-oriented style, with “methods” on “instances”.