Hi!
My main domain is C++ language, but in our company Erlang heavly used.
So I have a question about runtime compilation. I want to create lib where user can define some “pathes” to field in map and change it. Like
matcher:add_path([a, b, c, d], fun(Value) -> change(V) end).
matcher:add_path([a, b, e, f], fun(Value) -> change(V) end).
I can do that through reqursive match on map like
recursive_matcher([Key | Rest], Map) when is_map(Map) ->
case maps:find(Key, Map) of
{ok, Val} -> recursive_matcher(Rest, Val);
error -> nomatch
end;
recursive_matcher([], Val) -> {ok, Val};
recursive_matcher(_, _) -> error.
And I thought about dynamic compilation/reload and made POC like
% exact_matcher.erl
-module(exact_matcher).
-define(PT_ADDED, {?MODULE, added}).
add_path(Path) when is_list(Path), Path =/= [] ->
Pending = persistent_term:get(?PT_ADDED, []),
case lists:member(Path, Pending) of
true -> ok;
false -> persistent_term:put(?PT_ADDED, [Path | Pending])
end,
ok.
make_function_clause(Clause) when is_list(Clause) ->
Acc = "do(",
Acc2 = lists:foldl(fun(C, A) -> string:concat(A, io_lib:format("#{~s:=", [atom_to_list(C)])) end, Acc, Clause),
Val = string:concat(Acc2, "V"),
Finishing = string:concat(Val, lists:duplicate(length(Clause), $})),
list_to_binary(string:concat(Finishing, ") -> {oki,V};\n")).
compile() ->
Pending = persistent_term:get(?PT_ADDED, []),
{ok, F} = file:open("exact_matcher.erl", [write]),
file:write(F, <<"-module(exact_matcher).\n">>),
file:write(F, <<"-export([do/1]).\n">>),
[ file:write(F, make_function_clause(Clause)) || Clause <- Pending ],
file:write(F, <<"do(_) -> nomatch.\n">>),
file:close(F),
persistent_term:erase(?PT_ADDED),
{ok, length(Pending)}.
It will generate function clauses in exact_matcher.erl module
% add_path([a, b, c])
% compile()
% exact_matcher.erl (skipped header)
do(#{a => #{b => #{c => V}}}) -> V;
do(_) -> nomatch.
In my tests
test_req(Count) ->
timer:tc(fun() -> ntimes(fun() -> recursive_matcher([a, b, c], #{a => #{b => #{c => 1}}}) end, Count) end).
test_compiled(Count) ->
timer:tc(fun() -> ntimes(fun() -> exact_matcher:do(#{a => #{b => #{c => 1}}}) end, Count) end).
The compiled version in about 30+% faster then recursive matcher and for me it worth it.
But maybe this is bad idea and erlang runtime is not fit for this way of using.
My second thouht is make something like rebar plugin and user defines these pathes in some file, describe mutators, etc, and rebar will make this exact_matcher.erl on precompile phase and compile it with application. In that way there is no runtime compilation, but for me it fits also - user knows all the paths at compile time, no need for “full” runtime
What is the best option here?
Thanks