Tuple Calls

Oooh, I forget that not many Elixir people know what a Tuple Call is. :slight_smile:

Basically when you ‘call’ a variable in erlang it can be a few different types, here are some of them (in Elixir syntax, these all work as it is, normal Elixir/BEAM stuff):

iex(15)> defmodule Bloop do
...(15)>   def id(x), do: x
...(15)> end
{:module, Bloop,
 <<70, 79, 82, 49, 0, 0, 4, 144, 66, 69, 65, 77, 69, 120, 68, 99, 0, 0, 0, 143,
   131, 104, 2, 100, 0, 14, 101, 108, 105, 120, 105, 114, 95, 100, 111, 99, 115,
   95, 118, 49, 108, 0, 0, 0, 4, 104, 2, ...>>, {:id, 1}}
iex(16)> a = fn x -> x end
#Function<6.52032458/1 in :erl_eval.expr/5>
iex(17)> b = &Bloop.id/1
&Bloop.id/1
iex(18)> c = {Bloop, 1, 2, 3}
{Bloop, 1, 2, 3}
iex(19)> d = %{a: 42}
%{a: 42}
iex(20)> e = IO
IO
iex(21)> a.(42)
42
iex(22)> b.(42)
42
iex(23)> c.id() # The tuple call is added to the end of the argument list
{Bloop, 1, 2, 3}
iex(24)> d.a
42
iex(25)> e.inspect(42)
42
42

Basically when you ‘call’ a variable in Erlang it can be an Atom (Module), or a tuple (Tuple Call).

A Tuple Call is just a Tuple where the first element is a Module atom, can have any number of elements, and the entire tuple is passed in as the last argument. An example:

iex(26)> defmodule Blah do
...(26)>   def id(x), do: x
...(26)>   def new(val), do: {__MODULE__, val}
...(26)>   def add(v, {__MODULE__, val}), do: new(val+v)
...(26)>   def get({__MODULE__, val}), do: val
...(26)>   def inspect(opts\\[], {__MODULE__, val}), do: IO.inspect(val, opts)
...(26)> end
{:module, Blah,
 <<70, 79, 82, 49, 0, 0, 7, 144, 66, 69, 65, 77, 69, 120, 68, 99, 0, 0, 1, 132,
   131, 104, 2, 100, 0, 14, 101, 108, 105, 120, 105, 114, 95, 100, 111, 99, 115,
   95, 118, 49, 108, 0, 0, 0, 4, 104, 2, ...>>, {:inspect, 2}}
iex(27)> b = Blah.new(21)
{Blah, 21}
iex(28)> b = b.add(21)
{Blah, 42}
iex(29)> b.get()
42
iex(30)> b.inspect()
42
42
iex(31)> b.inspect(label: "Vwoop")
Vwoop: 42
42
iex(32)> Blah.get(b) # Can use it normally too, you do not need to call it with tuple-calls
42
iex(33)> b = Blah.add(21, b)
{Blah, 63}
iex(34)> Blah.get(b)
63

Basically where a normal atom call like:

iex(35)> a = IO
IO
iex(36)> a.inspect(42)
42
42

Let’s you call a module, a tuple-call let’s you associate data with and pass in data along with that module name without needing to carry it in multiple bindings as you’d have to do otherwise. Like say you had an Enum module that could work on any ‘type’, even user-defined, without the need for protocols while still being fast, tuple-calls could allow that without any protocol-magic, and anything that fulfills the interface @behaviour could be used with that module, thus replacing the entire Access type Elixir has as well, if only each container was wrapped in such a tuple-call (could special case primitives like [] and %{} of course), and a user-container could fulfill the same interface by just returning something like {MySpecialModule, mydata} instead of just mydata, which is perfect if it is supposed to be opaque anyway as most special containers are.

But tuple-calls fill a very nice niche that is just not as easy to do otherwise and I do wish Elixir took advantage of them instead of ignoring them. :slight_smile:

As well as it does already. Tuple calls work fine as it is.

5 Likes