Let me demonstrate why storing anon functions into persistent storage, say via DETS, is bad (you can store in ETS fine, just don’t persist it out and don’t hot-reload code):
iex(1)> defmodule Testering do
...(1)> def get, do: fn v -> v end
...(1)> end
{:module, Testering,
<<70, 79, 82, 49, 0, 0, 4, 220, 66, 69, 65, 77, 69, 120, 68, 99, 0, 0, 0, 126,
131, 104, 2, 100, 0, 14, 101, 108, 105, 120, 105, 114, 95, 100, 111, 99, 115,
95, 118, 49, 108, 0, 0, 0, 4, 104, 2, ...>>, {:get, 0}}
iex(2)> id = Testering.get()
#Function<0.108102960/1 in Testering.get/0>
iex(3)> b = :erlang.term_to_binary(id)
<<131, 112, 0, 0, 0, 81, 1, 206, 48, 166, 31, 173, 185, 46, 112, 247, 209, 191,
102, 180, 238, 231, 231, 0, 0, 0, 0, 0, 0, 0, 0, 100, 0, 16, 69, 108, 105,
120, 105, 114, 46, 84, 101, 115, 116, 101, 114, 105, 110, 103, ...>>
iex(4)> inspect(b, limit: :infinity)
"<<131, 112, 0, 0, 0, 81, 1, 206, 48, 166, 31, 173, 185, 46, 112, 247, 209, 191, 102, 180, 238, 231, 231, 0, 0, 0, 0, 0, 0, 0, 0, 100, 0, 16, 69, 108, 105, 120, 105, 114, 46, 84, 101, 115, 116, 101, 114, 105, 110, 103, 97, 0, 98, 6, 113, 133, 48, 103, 100, 0, 13, 110, 111, 110, 111, 100, 101, 64, 110, 111, 104, 111, 115, 116, 0, 0, 0, 80, 0, 0, 0, 0, 0>>"
So on expressions 1 and 2 I define a function in a module and return a new anonymous function from it. It is given the randomly auto-generated name of #Function<0.108102960/1 in Testering.get/0>
, and for all intents and purposes you can consider that as its name (internally it is more of a unique reference that gets even more complex if it is a closure).
On expression 3 I convert that anonymous function to a binary (and on expression 4 I print it out in full), which is precisely what ETS and DETS and mnesia and others do, so this is what is actually stored.
Now, the 131 at the start is basically the ‘version’ of the term format, so we can ignore it.
Next the 112 means that it is a NEW_FUN_EXT, which is defined as (we really really need a table syntax plugin for this Discourse install…):
1 4 1 16 4 4 N1 N2 N3 N4 N5
112 Size Arity Uniq Index NumFree Module OldIndex OldUniq Pid Free Vars
So if we decompose that with Erlang’s bit syntax:
iex(12)> <<131, 112, size::size(32), arity::size(8), uniq::size(128), index::size(32), numFree::size(32), rest::bitstring>> = b
<<131, 112, 0, 0, 0, 81, 1, 206, 48, 166, 31, 173, 185, 46, 112, 247, 209, 191,
102, 180, 238, 231, 231, 0, 0, 0, 0, 0, 0, 0, 0, 100, 0, 16, 69, 108, 105,
120, 105, 114, 46, 84, 101, 115, 116, 101, 114, 105, 110, 103, ...>>
iex(13)> {size, arity, uniq, index, numFree}
{81, 1, 274073566770734362952310774876330715111, 0, 0}
So the size
is the size in bytes of this function structure (the part after the <<131,112>>
, and it matches. The arity
is the number of arguments the function takes, 1 in this case, which is correct. The uniq
is the MD5 hash of the start of the significant part of the BEAM compiled file of the module where this anonymous function is defined to ensure corruption does not happen, and if this does not match the module that is loaded in memory then calling the anonymous function fails. Thus you can store and run an anonymous function as long as the module that it was defined in does not change, does not hot code swap, is not even recompiled, nothing at all, it must not change. Continuing though, the index
is the index into the function table of the module (see if you recompile then an anonymous function can change positions). And lastly the numFree
is what it is closing
around (this is a function, not a closure, so it is 0
here).
The internal system stores it similarly as well, thus calling an anonymous function that was defined in a module that has been recompiled or hot loaded or anything at all will fail.
A work-around (still not recommended) is to store the bytecode (if it is not a closure) or source code an execute it straight, as detailed prior in this thread, though that is not efficient by any stretch.