说起元编程,lisp的抽象能力无疑是最强的,独特的S-expression和macro,简直是居家旅行,杀人必备之神器= =
其实erlang的元编程能力也不弱。让我们一切先从smerl开始,慢慢了解erlang的meta programming
smerl是erlyweb项目中内部使用的一个模块,它可以让我们很容易的动态创建编译模块,动态添加function等等。
首先我们来热下身,先做个小功能,动态创建一个叫hello_world的模块,往中间添加一个echo函数,打印出"hello, world!"
-module(test). -compile(export_all). test_hello_world() -> M = smerl:new('hello_world'), {ok, M2} = smerl:add_func(M, "echo() -> io:format(\"hello, world!\")."), smerl:compile(M2).
我们将test模块编译,然后在命令行中运行hello_world:echo().可以顺利打印出"hello, world!"
接下来,我们来分析一下smerl的源码,上面那段代码是怎样完成我们的需求的
首先我们来看一看smerl:new/1:
%% @type meta_mod(). A data structure holding the abstract representation %% for a module. %% @type func_form(). The abstract form for the function, as described %% in the ERTS Users' manual. %% The record type holding the abstract representation for a module. -record(meta_mod, {module, file, exports = [], forms = [], export_all = false}). %% @doc Create a new meta_mod for a module with the given name. %% %% @spec new(Module::atom()) -> meta_mod() new(ModuleName) when is_atom(ModuleName) -> %填充meta_mod的module name #meta_mod{module = ModuleName}.
这段其实很简单啦, 就是填充一个元模块的record,成为smerl操作的上下文。
接下来,我们看一下,往模块中添加函数的过程。
%% @doc Add a new function to the meta_mod and return the resulting meta_mod. %% This function calls add_func(MetaMod, Form, true). %% %% @spec add_func(MetaMod::meta_mod(), Form::func_form() | string()) -> %% {ok, NewMod::meta_mod()} | {error, parse_error} add_func(MetaMod, Form) -> add_func(MetaMod, Form, true). %% @doc Add a new function to the meta_mod and return the new MetaMod %% record. Export is a boolean variable indicating if the function should %% be added to the module's exports. %% %% @spec add_func(MetaMod::meta_mod(), Func::func_form() | string(), %% Export::boolean()) -> %% {ok, NewMod::meta_mod()} | {error, parse_error} add_func(MetaMod, Func, Export) when is_list(Func) -> case parse_func_string(Func) of {ok, Form} -> add_func(MetaMod, Form, Export); Err -> Err end; add_func(MetaMod, {function, _Line, FuncName, Arity, _Clauses} = Form, true) -> Foo = {ok, MetaMod#meta_mod{ exports = [{FuncName, Arity} | MetaMod#meta_mod.exports], forms = [Form | MetaMod#meta_mod.forms] }}, Foo; add_func(MetaMod, {function, _Line, _FuncName, _Arity, _Clauses} = Form, false) -> {ok, MetaMod#meta_mod{forms = [Form | MetaMod#meta_mod.forms]}}; %%add_func(MetaMod, Name, Fun) when is_function(Fun) -> %% add_func(MetaMod, Name, Fun, true); add_func(_, _, _) -> {error, parse_error}. parse_func_string(Func) -> case erl_scan:string(Func) of {ok, Toks, _} -> case erl_parse:parse_form(Toks) of {ok, _Form} = Res -> Res; _Err -> {error, parse_error} end; _Err -> {error, parse_error} end.
上面的代码其实很简单,最核心的部分,在于parse_func_string/1函数,他利用erl_scan:string/1将一段字符串转换成了tokens,
接下来用erl_parse:parse_form/1将这段tokens解析成ASF
(就是erlang的erlang 的Abstract Format,关于ASF,大家可以移步erlang的文档http://www.erlang.org/doc/apps/erts/absform.html,这里就不冗余介绍)
最后将这段ASF填充入meta_mod这个record的forms段中。其实请注意到现在为止,我们也只是对meta_mod这个上下文数据进行了修改。
接下来,我们来看看编译过程smerl:compile/1,2:
%% @doc Compile the module represented by the meta_mod and load the %% resulting BEAM into the emulator. This function calls %% compile(MetaMod, [report_errors, report_warnings]). %% %% @spec compile(MetaMod::meta_mod()) -> ok | {error, Error} compile(MetaMod) -> compile(MetaMod, undefined). %% @doc Compile the module represented by the meta_mod and load the %% resulting BEAM into the emulator. 'Options' is a list of options as %% described in the 'compile' module in the Erlang documentation. %% %% If the 'outdir' option is provided, %% the .beam file is written to the destination directory. %% %% @spec compile(MetaMod::meta_mod(), Options::[term()]) -> ok | {error, Error} compile(MetaMod, undefined) -> compile(MetaMod, [report_errors, report_warnings, return_errors]); compile(MetaMod, Options) -> %根据传入的meta_mod,和编译选项进行编译 Forms = [{attribute, 2, module, MetaMod#meta_mod.module}, %添加-module属性的asf。 {attribute, 3, export, get_exports(MetaMod)}], %以及-export属性的asf FileName = case MetaMod#meta_mod.file of undefined -> atom_to_list(get_module(MetaMod)); Val -> Val end, Forms1 = [{attribute, 1, file, {FileName, 1}} | Forms], %添加-file属性的asf Forms2 = Forms1 ++ lists:reverse(MetaMod#meta_mod.forms), %添加元模块结构中的相关其他asf。 case compile:forms(Forms2, Options) of %进行编译 {ok, Module, Bin} -> Res = case lists:keysearch(outdir, 1, Options) of {value, {outdir, OutDir}} -> %根据编译选项确定是否输出beam文件 file:write_file( OutDir ++ ['/' | atom_to_list(MetaMod#meta_mod.module)] ++ ".beam", Bin); false -> ok end, case Res of ok -> code:purge(Module), %清理旧代码 case code:load_binary( %载入新编译的模块 Module, atom_to_list(Module) ++ ".erl", Bin) of {module, _Module} -> ok; Err -> Err end; Err -> Err end; Err -> Err end.
我在上面的代码里加入了必要的注释,到现在为止,大家可以看到,我们动态修改模块,其实本质上,利用的是ASF。
erl_scan和erl_parse相关模块可以帮助我们将一段字符串转变为所需要的ASF
而在编译时,compile模块可以通过这个ASF中间层,顺利得到编译后可以运行的字节码。
OK,这篇就到这里,抛砖引玉而已,关于erlang元编程能力的体现,我会在后面的blog里慢慢展开。