The Pug Automatic

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

Written October 10, 2015. Tagged Elixir.

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

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

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

This, in turn, is interpreted as an function call, with the tuple itself as the last argument:"Cat"), {Expect, pet})

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

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

then it would be interpreted as, 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:

defmodule Expect do
def to({:be, value}, {Expect, value}) do
IO.puts("Hooray, they're both '#{value}'!")

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

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

defp expect(actual) do
{Expect, actual}

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


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".