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.
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.
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