4

I'd like to use Lisp Flavored Erlang as a scripting extension language for an Erlang application. If you want, in a similar way GNU Emacs is configured and extended via Emacs Lisp.

I know that the argument is wide and structured; but in the specific case of this question I'd like being able to read a binding name (or variable, if you prefer) defined in LFE from Erlang code.

I'm not an expert of LFE internal architecture (which is an excellent example of software engineering and Erlang programming), but I was not able to find an answer neither in the sources nor in the documentation. Looking at sources I can see that LFE contains both a compiler that target Erlang VM and an interpreter. The latter is the one I'm trying to use.

If I start Erlang shell/REPL in LFE installation path (on my system $HOME/opt/lfe):


$ cd /path/to/LFE-install-dir
$ erl -pa ./ebin

I'm able to calculate a value:


1> {ok, Expr} = lfe_io:read_string("(+ 1 10)").
{ok,['+',1,10]}
2> Result = lfe_eval:expr(Expr).
11

This is a first step, but not exactly what I want. I'd like rather to bind a variable and read its value; that's my issue:


3> {ok, Expr2} = lfe_io:read_string("(set a 10)").
{ok,[set,a,10]}
4> lfe_eval:expr(Expr2).
** exception error: {unbound_func,{set,2}}
     in function  lfe_eval:eval_expr/2

Why set is recognized as an unbound function? In LFE REPL this expression is valid:

Erlang/OTP 17 [erts-6.4] [source] [64-bit] [smp:4:4] ...
LFE Shell V6.4 (abort with ^G)
> (set a 10)
10
> a
10

I'm obviously using the API in the wrong way. How can I read the content of a and/or properly initialize the LFE interpreter?

(If this is explained somewhere, please provide the reference).

Community
  • 1
  • 1
gsscoder
  • 3,088
  • 4
  • 33
  • 49
  • 2
    The reason is that `set` is a shell command, it doesn't exist outside the shell. Normally in LFE code you bind variables using `let`, `case, `lambda`, `match-lambda` or other places where you have pattern matching. Can you give a better example of how you would like to use it? – rvirding Jun 04 '15 at 23:01
  • +1 Thanks for clarification. I'm learning various **Lisp**s and found **LFE** the most interesting 'cause lives in Erlang ecosystem. Now I'm trying to experimenting various ideas, but basically I want to be able to use the system as a configuration DSL for Erlang apps. Suppose my app is a server and I want the **connection timeout** to be handled externally in my **config.lfe**, what's the best way to follow? Should I define a `get-conn-timeout` function and read its result or better create a `set_conn_timeout` from Erlang and attach it to the LFE session created for reading the configuration? – gsscoder Jun 05 '15 at 05:00
  • Since this question is quoted in a _LFE google group post_, I think it could be useful for those who are interested in these arguments link back to the group thread from here. [Lisp Flavored Erlang](https://groups.google.com/forum/#!forum/lisp-flavoured-erlang) **>** [LFE configuration script, LCFG and the likes](https://groups.google.com/forum/#!topic/lisp-flavoured-erlang/S5s6c5DovEE). – gsscoder Jun 07 '15 at 11:45

2 Answers2

2

I won't attempt to answer completely your broader question about the "best practices" of adding scripting. It seems to me that choosing between "hook-based" solution (in which you define hook implementations by name convention and they are automatically recognized) and "explicit api" solution (in which you use functions predefinied in the scripting enviroment to register your hooks or otherwise call configuration functions) is largely a matter of taste. Explicit calls like (set-connection-timeout-handler ...) may be more readable, easier to debug (no misspelling problems, no surprises on api changes), easier to document, and a bit more flexible, but more, well, explicit.

Building from your simple variable definition example, here are a few ways you could get started going further the "interpreted" path:

1> {ok, Expr} = lfe_io:read_string("'((a 10))").
{ok,[quote,[[a,10]]]}
2> lfe_eval:expr (Expr).
[[a,10]]

3> EvalAll = fun (Conf) -> {ok, E} = lfe_io:read_string("'(" ++ Conf ++ ")"), lfe_eval:expr(E) end.
#Fun<erl_eval.6.90072148>
4> EvalAll ("(a 10) (b 11)").                                                                      
[[a,10],[b,11]]

5> EvalAllL = fun (Conf) -> {ok, E} = lfe_io:read_string("(list " ++ Conf ++ ")"), lfe_eval:expr(E) end.
#Fun<erl_eval.6.90072148>
6> [{f, F}] = EvalAllL ("(tuple 'f (lambda (x) (+ 10 x)))").
[{f,#Fun<lfe_eval.12.2018457>}]
7> F (12).
22

8> G = fun (X) -> X * 2 end.
#Fun<erl_eval.6.90072148>
9> lfe_eval:expr (element (2, lfe_io:read_string ("(g 15)")), lfe_eval:add_lexical_func(g, 1, G, lfe_env:new ())).
30
Nicole A.
  • 306
  • 1
  • 4
  • +1 This answers my question and puts me in the right path. Before getting this reply, I was reasoning about the two scripting patterns you quoted, ending that probably the best solution could be return a data structure from LFE (like a `tuple`). You explain this and a lot more... The reply really helped me. It would be great to have this explained in LFE docs or also in a blog post. The cool thing I want to point out is that this can be done in LFE itself (or from Elixir)! Erlang ecosystem is truly mature and powerful. – gsscoder Jun 05 '15 at 19:29
  • Your reply, the others and discussions on google group gave born to this: https://github.com/gsscoder/cup – gsscoder Jun 10 '15 at 03:59
2

A simple way is use to the 'lfe' command to run LFE scripts. The 'lfe' behaves in similar fashion to 'bash' in that you can use it to run scripts. The 'lfec' script defined in bin/lfec is a good example of this. It is an LFE shell script which parses it arguments and run the LFE compiler.

rvirding
  • 20,848
  • 2
  • 37
  • 56
  • 1+ Yes, `bin/lfec` is an interesting example of using `lfe_comp:file/2` that if I'm not wrong compiles the script behind scenes. LFE is great as Lisp environment that targets Erlang VM, but I think that exposing itself using **CaaS** (compiler as as service) pattern is a very cool thing. – gsscoder Jun 06 '15 at 04:54
  • 1
    @gsscoder almost, `lfe_comp:file/2` compiles the file which is the argument to `lfec`, the script itself is interpreted. – rvirding Jun 09 '15 at 20:35
  • @rivirding You mean *compiled* as it's turned into bytecode for BEAM? – gsscoder Jun 10 '15 at 03:57
  • 1
    @gsscoder Yes, it is a stand-alone LFE compiler which compiles a .lfe file into a .beam file in the same way that erlc compiles .erl file into .beam files. – rvirding Jun 15 '15 at 20:33