Idiom: wrapping a module in a tuple

I was reading the sources of ESpec and I’ve come across an idiom I have not encountered before. That is, wrapping a module in a one-element tuple when passing it to a fn. For example, it’s used to impement ESpec’s configuration API:

ESpec.configure fn(config) ->
  config.before fn(tags) ->
    {:shared, tags: tags}
  end

  config.finally fn(_shared) ->
    :ok
  end
end

In that snippet above, I can confirm that config, the argument of the outer fn, is indeed the one-element tuple {ESpec.Configuration}.

Trying to wrap my head around it, I’ve found that it looks like it will use the wrapped module as both the receiver [1] and the argument of the function.

{Date}.utc_today
** (UndefinedFunctionError) function Date.utc_today/1 is undefined or private. Did you mean one of:

      * utc_today/0

    (elixir) Date.utc_today({Date})

That looks quite unexpected. Can anyone please explain how this works? Where can I find some source material?

Thanks!


[1] I know, I know, “receiver” makes more sense when talking about objects and methods. Is there an equivalent expression in functional programming?

Ok, so it looks like the wrapped module will be inserted as the last argument of the function.
For example:

defmodule Foo do
  def bar(val, {Foo}) do
    "1 - #{val}"
  end

  def bar(val, _) do
    "2 - #{val}"
  end

  def bar(val) do
    "3 - #{val}"
  end
end

Foo.bar(:hello)   # "3 - hello"
{Foo}.bar(:hello) # "1 - hello"

It looks like a useful feature, but the interface is very obscure. Is it meant to be a private internal of the compiler?

Seems relevant: Tuple Calls

1 Like

I see, thanks!

It’s a feature of Erlang left from the time Erlang had parametrized modules. It’s also most probably going away on OTP 21 and will be hidden behind a @compile attribute. Reference: https://github.com/erlang/otp/pull/1499

2 Likes

It’s good riddance to it. Parametrised modules were bad enough but keeping tuple modules was an unfortunate necessity for BC.

1 Like

I really quite like them, they let you do something like OCaml’s First Class modules and Functors but on the BEAM, without them you have to fall back to carrying state as well as the module to pass it to, which is double the data that the user has to do…

I mean as covered in the other thread, the main cost is that it bifurcates function call syntax into “calling functions on the module in question” and “calling functions on other modules”.