8

I'm trying to get around a problem with file:consult/1 not allowing tuples with fun in them like in this example:

{add_one, fun(X) -> X+1 end}.

To get around this I'm considering writing the fun inside a string and evaluating it

{add_one, "fun(X) -> X+1 end"}.

The question is. How do I convert the string into a fun?

Jon Gretar
  • 5,372
  • 1
  • 23
  • 22

5 Answers5

11
parse_fun_expr(S) ->
  {ok, Ts, _} = erl_scan:string(S),
  {ok, Exprs} = erl_parse:parse_exprs(Ts),
  {value, Fun, _} = erl_eval:exprs(Exprs, []),
  Fun.

Note that you need a period at the end of your fun expression, e.g. S = "fun(X) -> X + 1 end.".

Zed
  • 57,028
  • 9
  • 76
  • 100
6

file:script/1 almost does what you want - it evaluates a series of erlang expressions from a file and returns the last result. You could use it in place of file:consult/1 but you'd need to change the format of the file from "term. term. term." giving [term, term ,term] to "[term, term , term]." giving [term, term, term] - place a single expression in the file instead of a sequence.

archaelus
  • 7,109
  • 26
  • 37
  • 1
    I think this is a better solution. If there is a library function which does what you want then you should use it. Also parsing strings when you don't need to seems a bit off. – rvirding Jan 06 '10 at 03:04
  • Ahh. I may consider doing that instead. I had my head so stuck in file:consult that I forgot to consider alternatives on that part. – Jon Gretar Jan 07 '10 at 11:26
  • One extra question though. How can I use records in file:script? – Jon Gretar Jan 07 '10 at 11:41
  • I don't think you can use records in file:script -- you can pass in a set of bindings, but I think it expects records to be replaced at this point. To use records you'd need to parse it yourself, expand out the records ala erl_expand_records and then run it through erl_eval. Someone should wrap all those stages up into a better file:consult/eval really. – archaelus Jan 08 '10 at 11:24
  • Yeah I figured. But thanks. file:script/1 was a much better way than the direction I was heading. – Jon Gretar Jan 08 '10 at 12:59
2

I'd like to point out that Zed's answer creates an interpreted fun. When the fun is called it enters the evaluator which starts to evaluates the abstract syntax tree returned by erl_parse:parse_exprs/1 that it has captured. Looking at the fun created:

11> erlang:fun_info(Fun, env).
{env,[[],none,none,
      [{clause,1,
               [{var,1,'X'}],
               [],
               [{op,1,'+',{var,1,'X'},{integer,1,1}}]}]]}
12> erlang:fun_info(Fun, module).
{module,erl_eval}

One can see that it has closed over the parsed abstract syntax tree as seen in the env info, and it is a fun created inside erlang_eval as seen in the module info.

It is possible to use the erlang compiler to create a compiled module at runtime, and a pointer toward that is compile:forms/2 and code:load_binary/3. But the details of that should probably go into another stackoverflow question.

Christian
  • 9,417
  • 1
  • 39
  • 48
  • 1
    Thanks for pointing that out. Here is an example of compiling forms: http://stackoverflow.com/questions/1974236/string-to-abstract-syntax-tree. – Zed Jan 06 '10 at 07:32
0

Maybe by using the erl_eval module?

jldupont
  • 93,734
  • 56
  • 203
  • 318
  • Yeah I was lead to that. However not sure on how to use it. It takes expressions generated by erl_parse but using erl_parse I usually end up with an Expression of a string. – Jon Gretar Jan 05 '10 at 21:16
0
2> F =fun(Str,Binding) ->
{ok,Ts,_} = erl_scan:string(Str),
Ts1 = case lists:reverse(Ts) of
          [{dot,_}|_] -> Ts;
          TsR -> lists:reverse([{dot,1} | TsR])
      end,
{ok,Expr} = erl_parse:parse_exprs(Ts1),
erl_eval:exprs(Expr, Binding) end.
#Fun<erl_eval.12.111823515>
3> F("A=23.",[]).
{value,23,[{'A',23}]}

5> F("12+B.",[{'B',23}]).
{value,35,[{'B',23}]}
legoscia
  • 39,593
  • 22
  • 116
  • 167
ligaoren
  • 1,043
  • 1
  • 9
  • 10