Memoization in Erlang?
Ulf Wiger (AL/EAB)
ulf.wiger@REDACTED
Wed Feb 1 09:39:52 CET 2006
I'm not going to comment on the general approach,
just answer the specific questions.
Pete Kazmier wrote:
> 1) Writing higher-order functions seems to be complicated
> in Erlang because there is no support for varargs.
> Thus, in my example, I have this kludge to return an
> N-arg closure. Is there a better way of handling this
> keeping in mind that the wrapper function
> should have the same interface as the memoized one?
In this particular case, perhaps apply(Module, Function, Args)
would be better than using funs?
> 2) In my kludge, I use erlang:fun_info/1 which states that
> it is intended for debugging use. Does this preclude
> its use in my code? Should I instead just define
> memoize1/1, memoize2/1, memoize3/1, etc ...
Instead of fun_info/1, you could use the guard function
is_function(F, Arity), i.e.
if is_function(F, 0) ->
fun() -> callit(Pid, []) end;
is_function(F, 1) ->
fun(A1) -> callit(Pid, [A1]) end;
is_function(F, 2) ->
...
end.
> 3) In lieu of my kludge, I started to wonder if I could
> dynamically construct a string of code, then somehow
> compile that on the fly, and return the resulting
> function to the caller. Is this even possible?
It is.
Here's an example of how to generate "forms", compiling
them, and then loading them into a running system:
case generate_bin(Tabs, 1) of
{ok, ?MOD, Bin} ->
code:purge(?MOD),
{module, ?MOD} =
code:load_binary(?MOD, OutFile, Bin);
{error, Reason} ->
erlang:error(
{Reason, erlang:get_stacktrace()})
end
generate_bin(Tabs, L1) ->
[{attribute,_,file,_}|_] = Forms = codegen(Tabs, L1),
compile:forms(Forms, [no_warnings]).
codegen(Tabs, L1) ->
L2 = L1+2, L3 = L2+1,
{IndexFun, L4} = codegen_indexes(Tabs, L3+2),
{RefsFun, L5} = codegen_references(Tabs, L4+2),
{TIFun, L6} = codegen_table_info(Tabs, L5+2),
{ModFun, L7} = codegen_module(Tabs, L6+2),
{AclFun, L8} = codegen_acl(Tabs, L7+2),
{AccessFun, L9} = codegen_access(Tabs, L8+2),
{TabFuns, L10} = codegen_tabs(Tabs, L9+2),
{TabPropFuns, L11} = codegen_tab_props(Tabs, L10+2),
[{attribute,L1, file, {"rdbms_verify_jit.erl",1}},
{attribute,L2, module, rdbms_verify_jit},
{attribute,L3, export, [{validate_rec, 2},
{indexes, 1},
{references, 1},
{acl, 1},
{verify_access, 3},
{module, 1},
{table_info, 2},
{table_property, 2}]} |
IndexFun
++ RefsFun
++ TIFun
++ ModFun
++ AclFun
++ AccessFun
++ TabFuns
++ TabPropFuns
++ [{eof, L11+1}]].
codegen_indexes(Tabs, L1) ->
{Body, L2} = lists:mapfoldl(
fun(Tab, L) ->
Indexes = indexes(Tab),
{{clause, L, [{atom, L, Tab}],
[],
[erl_parse:abstract(Indexes, L+1)]},
L+2}
end, L1+1, Tabs),
{[{function, L1, indexes, 1, Body}], L2}.
(I actually don't think one has to bother about getting
the line numbers right, but I think it helps if/when
your generated code doesn't compile.)
Pretty-printing the forms can be done like this:
generate_src_file(File, Tabs, L1) ->
[{attribute,_,file,_}|_] = Forms = codegen(Tabs, L1),
Out = [[erl_pp:form(F), "\n"] || F <- tl(Forms)],
You can also generate code using the erl_syntax module.
The best way to understand what forms to generate may be
to first write a sample program by hand, and compiling
it with 'erlc +debug_info ...'. Then, you can fire up
an erlang shell and extract the parsed forms using
beam_lib:chunks("mymod.beam", [abstract_code]).
The output will most likely be truncated. Use e.g.
io:format("~p~n", [beam_lib:chunks(...)]).
> If so, would it have access to the closed over
> variable?
Well, when you generate code at runtime, you should be
able to solve that problem to your liking.
You can also use erl_eval. See e.g. file:script/[1,2]:
eval_stream(Fd, Handling, Last, E, Bs) ->
eval_stream(io:parse_erl_exprs(Fd, ''), Fd, Handling, Last, E, Bs).
eval_stream({ok,Form,EndLine}, Fd, Handling, Last, E, Bs0) ->
case catch erl_eval:exprs(Form, Bs0) of
{value,V,Bs} ->
eval_stream(Fd, Handling, {V}, E, Bs);
{'EXIT',Reason} ->
eval_stream(Fd, Handling, Last,
[{EndLine,?MODULE,Reason}|E], Bs0)
end;
eval_stream({error,What,_EndLine}, Fd, H, L, E, Bs) ->
eval_stream(Fd, H, L, [What | E], Bs);
eval_stream({eof,_EndLine}, _Fd, H, Last, E, _Bs) ->
case {H, Last, E} of
{return, {Val}, []} ->
{ok, Val};
{return, undefined, E} ->
{error, hd(lists:reverse(E, [{error, undefined_script}]))};
{ignore, _, []} ->
ok;
{_, _, [_|_] = E} ->
{error, hd(lists:reverse(E))}
end.
(The Bs variable is a list of variable bindings passed
to erl_eval.)
> 4) Are there better ways to implement memoization
> in Erlang? Would using the process dictionary
> be a bad idea? My first thought was to store
> the cached values in it, and then the whole
> thing could be implemented in a single function
> without the use of processes.
Using the process dictionary in a process that you
control altogether is not necessarily a bad idea,
but in your example code, I don't think you'd have
trouble using e.g. dict.erl and caching the data
in a dictionary 'loop variable' instead.
Some modules, like 'random' and 'mnesia', use the
process dictionary of the current process, so it's
not unheard of. I don't think it's a practice that
should be encouraged, though.
Regards,
Ulf Wiger
More information about the erlang-questions
mailing list