Bad Ideas : Elixir

Global Getter/Setter

Continuing with the theme of things you shouldn't do and weren't going to do anyway, today I will illustrate creating global get and set functions.

Can you take this seriously? I cannot but I will pretend

I will use a simple macro to create set/1. The macro will define a module with a get/0 function.


defmodule GetterSetter do
  defmacro set(val) do
    quote do
      defmodule Get do
        def get() do
          unquote(val)
        end
      end
    end
  end
  def get(), do: Get.get()
end

All that nesting should clue you in that this is bad practice and also dumb.

Anyway the macro was necessary because of scoping. We could define the module inside a function just as well, but the value passed into the function would not be available within the defmodule's do block's scope. Hence unquote

Trying it out:


iex> import GetterSetter
nil
iex> set(8)
{:module, Get,
<<70, 79, 82, 49, 0, 0, 4, 160, 66, 69, 65, 77, 69, 120, 68, 99, 0, 0, 0,
131, 104, 2, 100, 0, 14, 101, 108, 105, 120, 105, 114, 95, 100, 111, 99,
95, 118, 49, 108, 0, 0, 0, 4, 104, 2, ...>>, {:get, 0}}
iex> get()
8
iex> set(:foo)
iex: warning: redefining module Get
{:module, Get,
<<70, 79, 82, 49, 0, 0, 4, 164, 66, 69, 65, 77, 69, 120, 68, 99, 0, 0, 0,
131, 104, 2, 100, 0, 14, 101, 108, 105, 120, 105, 114, 95, 100, 111, 99,
95, 118, 49, 108, 0, 0, 0, 4, 104, 2, ...>>, {:get, 0}}
iex> get()
:foo

Get some nice warnings telling us we are well outside expected practice, but get/0 and set/1 work beautifully. And because module definitions are available to all processes on a node the values are available globally.


iex> pid = spawn fn -> receive do :get -> IO.puts(get()) end end
#PID<0.257.0>
iex> set(:horsefeathers)
iex: warning: redefining module Get
{:module, Get,
<<70, 79, 82, 49, 0, 0, 4, 168, 66, 69, 65, 77, 69, 120, 68, 99, 0, 0, 0,
131, 104, 2, 100, 0, 14, 101, 108, 105, 120, 105, 114, 95, 100, 111, 99,
95, 118, 49, 108, 0, 0, 0, 4, 104, 2, ...>>, {:get, 0}}
iex> send(pid, :get)
horsefeathers
:get

Best practice would be to use GenServer or ets. As the warning indicates redefining modules should not be done and I assume it is not an error only because it is useful when experimenting in and iex session.

On the other hand the ability to programatically generate modules and especially function definitions within modules is fantastic. Have a look at String.Unicode to see what I mean.


Update

I have been informed that the above technique, wile undoubtedly a hack, can be useful. It is used in mochiweb for example. In the docstring they link this post on the erlang-questions mailinglist by way of explanation:

If the entire tables are constant over a long time, you could generate them as modules. Nowadays, compile-time constants (even complex ones) are placed in a constant pool associated with the module. So, you could generate something like this:

 -module(autogen_table).
 -export([find/1]).
 find(0) -> {some_struct, "hello", ...};
 ...
 find(99) -> {some_other_struct, <<"hi!">>}
 find(X) -> throw({not_found, X}).

As far as I know, these constants will not be copied to the private heaps of the processes. The generated code will also give you the fastest possible lookup.

/Richard