Iterating Tuples in Elixir
· 3 min read
According to erlang docs ,
A tuple is a compound data type with a fixed number of terms:
{Term1,...,TermN}
Each term
Termin the tuple is called an element . The number of elements is said to be the size of the tuple.
Tuple
s are known to be faster than
List
s (and even
Map
s)
for elementsโ direct access. Also, some libraries/packages often format
a response as tuples.
So,
Tuple
s are good. Save for one single glitch: they are not iterable.
That said,
Tuple
s do not implement
Enumerable
protocol. The goal of this
small post is to show how this protocol might be implemented for tuples
in non-naiฬve way (the naiฬve way would be to just convert tuples to lists.)
defmodule Tuple.Enumerable do
defimpl Enumerable, for: Tuple do
@max_items 42
def count(tuple), do: tuple_size(tuple)
# member? implementation is done through casting tuple to the list
# itโs not required for iteration, and building all those matched
# clauses seems to be an overkill here
def member?([], _), do: {:ok, false}
def member?(tuple, value) when is_tuple(tuple) do
tuple |> Tuple.to_list |> member?(value)
end
def member?(tuple, value) when is_list(tuple) do
for [h | t] <- tuple do
if h == value, do: {:ok, true}, else: member?(t, value)
end
end
def reduce(tuple, acc, fun) do
do_reduce(tuple, acc, fun)
end
defp do_reduce(_, {:halt, acc}, _fun), do: {:halted, acc}
defp do_reduce(tuple, {:suspend, acc}, fun) do
{:suspended, acc, &do_reduce(tuple, &1, fun)}
end
defp do_reduce({}, {:cont, acc}, _fun), do: {:done, acc}
defp do_reduce({value}, {:cont, acc}, fun), do: do_reduce({}, fun.(value, acc), fun)
Enum.each(1..@max_items-1, fn tot ->
tail = Enum.join(Enum.map(1..tot, & "e_#{&1}"), ",")
match = Enum.join(["value"] ++ [tail], ",")
Code.eval_string(
"defp do_reduce({#{match}}, {:cont, acc}, fun), do: do_reduce({#{tail}}, fun.(value, acc), fun)", [], __ENV__
)
end)
# list fallback for huge tuples
defp do_reduce([h | t], {:cont, acc}, fun) do
do_reduce((if Enum.count(t) <= @max_items, do: List.to_tuple(t), else: t), fun.(h, acc), fun)
end
# fallback to list for huge tuples
defp do_reduce(huge, {:cont, acc}, fun) when huge > @max_items do
do_reduce(Tuple.to_list(huge), {:cont, acc}, fun)
end
end
end
The code above builds
@max_items
function match clauses for
Tuple
s
having not more than
42
members. That guarantees no conversion to
list
happens for those.
Huge
Tuple
s, having more than
42
members, will be converted to lists
before operating.
The approach above might be useful in the case when one needs to use
Tuple
s for quick access: changing
@max_items
to super high value
would drastically increase
the compilation time
, while the
execution
time
will remain very impressive, comparing to
List
s.