Erlang eval and dynamic dispatch
George Malamidis, May 18th, 2008Ruby’s Object#send method offers an elegant alternative for invoking methods based on a command translating a symbol to a function dispatch. I was looking for similar functionality in Erlang and here’s what I came up with.
First, let’s see how we can achieve eval functionality in Erlang, i.e. evaluate strings as Erlang code at runtime.
-module (meta).
-export ([eval/2]).
eval(Code, Args) ->
{ok, Scanned, _} = erl_scan:string(Code),
{ok, Parsed} = erl_parse:parse_exprs(Scanned),
Bindings = lists:foldl(fun ({Key, Val}, BindingsAccumulator) ->
erl_eval:add_binding(Key, Val, BindingsAccumulator)
end, erl_eval:new_bindings(), Args),
{value, Result, _} = erl_eval:exprs(Parsed, Bindings),
Result.
erl_scan is Erlang’s token scanner module. The string function tokenizes a list of characters. erl_parse is the Erlang parser module and the parse_exprs function parses a list of tokens, each Token representing an expression. It returns a list of the abstract forms of the parsed expressions, ready to be used with erl_eval, the Erlang meta interpreter. An arbitrary list of bindings can be provided alongside the parsed expressions to erl_eval:exprs.
With the meta:eval function in place, we can evaluate arbitrary strings of code at runtime.
Eshell V5.6 (abort with ^G)
1> c(meta).
{ok,meta}
2> meta:eval("20 + 30.", []).
50
3> meta:eval("A + B.", [{'A', 15}, {'B', 60}]).
75
We can build on meta:eval to achieve Ruby-like dynamic dispatches.
send(MethodName, Args) ->
ArgNames = lists:foldl(fun ({K, _}, Acc) -> lists:append([K], Acc) end, [], Args),
Code = atom_to_list(MethodName) ++ "(" ++ atom_join(ArgNames, $,) ++ ").",
eval(Code, Args).
The send function takes two arguments, an atom which is the name of the function to be dispatched and a list of tuples with key/value pairs for the arguments to be passed to the function. atom_join joins a list of atoms into one string using the supplied separator.
atom_join([], _Sep) -> []; atom_join(Items, Sep) -> lists:flatten(atom_join1(Items, Sep, [])). atom_join1([Head | []], _Sep, Acc) -> [atom_to_list(Head) | Acc]; atom_join1([Head | Tail], Sep, Acc) -> atom_join1(Tail, Sep, [Sep, atom_to_list(Head) | Acc]).
Let’s add a couple of test functions to showcase what has been achieved.
hello() -> "hello, world". hello(Who) -> "hello, " ++ Who.
Back to Eshell…
Eshell V5.6 (abort with ^G)
1> c(meta).
{ok,meta}
2> meta:send('meta:hello', []).
"hello, world"
3> meta:send('meta:hello', [{'Name', "rock"}]).
"hello, rock"

May 18th, 2008 at 1:19 pm
I get what you’re doing but outside of the novelty factor, I don’t really see why this is useful. You can get largely the same behavior using regular pattern matching and guards:
-module(test).
-compile([export_all]).
hello() ->
“hello, world”.
hello(Who) when is_list(Who) ->
“hello, ” ++ Who.
—-
1> M = test.
test
2> F = hello.
hello
3> M:F().
“hello, world”
4> M:F(“Kevin”).
“hello, Kevin”
5>
May 20th, 2008 at 12:27 am
[...] Erlang eval and dynamic dispatch (tags: erlang programming) [...]