Vivian
Vivian

Reputation: 1639

Use a tuple as arguments for a function in Elixir

I'm writing a game engine in Elixir. (Yes, I know it's not a language inherently suited to that – the point is to look at how the use of an atypical language affects the structure of the result.)

As such, I have several supervisors to be run on game start – but what exactly they should be supervising depends on the game. My thought had been to have the user list out the necessary children, plus arguments and options, in the config.exs file as a list of tuples, and then the supervisor itself would simply pull those tuples from the application environment and use their contents as arguments to worker\2 (or worker\3, as appropriate).

However, I can't find any Elixir equivalent to Python's tuple-unpacking. I could do it myself for this specific case with a simple function:

def unpack_worker({module, args}) do
  worker(module, args)
end

def unpack_worker({module, args, opts}) do
  worker(module, args, opts)
end

But that feels clumsy at best, and would have to be written again for every function for which I might need this kind of configurability.

Upvotes: 3

Views: 3760

Answers (3)

Dogbert
Dogbert

Reputation: 222118

I believe you're looking for Tuple.to_list/1 and apply/3:

With those, you can invoke a function of the correct arity based on the contents of the tuple:

def unpack_worker(args) do
  apply(__MODULE__, :worker, Tuple.to_list(args))
end

If you now call unpack_worker({}), it'll call worker(), unpack_worker({:foo}) will call worker(:foo), and so on.

Demo:

defmodule A do
  def worker, do: IO.puts 0
  def worker(_), do: IO.puts 1
  def worker(_, _), do: IO.puts 2
  def worker(_, _, _), do: IO.puts 3

  def unpack_worker(tuple), do: apply(__MODULE__, :worker, Tuple.to_list(tuple))
end

A.unpack_worker({})
A.unpack_worker({:a})
A.unpack_worker({:a, :b})
A.unpack_worker({:a, :b, :c})

Output:

0
1
2
3

Upvotes: 8

Justin Wood
Justin Wood

Reputation: 10051

I believe you will want a combination of elem/2 and tuple_size/1 functions.

iex(1)> a = {:foo, :bar, :baz}
{:foo, :bar, :baz}
iex(2)> tuple_size(a)
3
iex(3)> elem(a, 0)
:foo
iex(4)> elem(a, 5)
** (ArgumentError) argument error
    :erlang.element(6, {:foo, :bar, :baz})

Just note that if you ask for an element at an index that does not exist in the tuple, you do get an argument error. Which means you are still going to have to use an if / case / cond / multiple function heads or something to differentiate what you are attempting to do.

Upvotes: 0

Elfred
Elfred

Reputation: 3851

I think you need to know the size of a tuple to properly access the elements. Maybe a better data structure for your use case would be a keyword list, especially considering it's what you get from config anyway? Then you can unpack worker:, args: and leave the remainder as options?

Upvotes: 1

Related Questions