LFE Quick Start with rebar3_lfe






by Duncan McGreggor











publisher logo

Published by Cowboys 'N' Beans Books

https://github.com/cnbbookshttp://cnbb.pub/info@cnbb.pub




First electronic edition published: 2013

Second electronic edition published: 2020




© 2013-2020, Duncan McGreggor

This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License

Creative Commons License




Cover art: Original version from 1968 DEC FOCAL Programming Manual with stylised PDP-8 as the dominant cover graphic. Adaptation of the original as a spoof and homage was made in 2015 by Duncan McGreggor.

Introduction

This is a version of the LFE quick start that has been re-imagined with the LFE rebar3 plugin in mind.

We will cover the following:

  • How to get started quickly with LFE using just rebar3 and your local installation of Erlang
  • Creating a new LFE project
  • Looking at LFE code in the REPL and in modules
  • Provide a little insight on how this works
  • Leave you with resources for jumping into LFE in more detail

About LFE

LFE, or "Lisp Flavoured Erlang", is a Lisp-2 (thus similar to Common Lisp, and MACLISP before that) that runs on top of the Erlang VM or "BEAM". It is the oldest sibling of the many BEAM languages that have been released since its inception (e.g., Elixir, Joxa, Luerl, Erlog, Haskerl, Clojerl, Hamler, etc.).

As a Lisp-2, LFE has different namespaces for functions and variables. Furthermore, LFE has Erlang's notion of arity, so functions with different arity may share the same name.

Lisps are great, but the thing that makes LFE so incredible is that it runs on the Erlang VM and is 100% compatible with Core Erlang.

About Erlang

Erlang is a programming language used to build massively scalable soft real-time systems with requirements on high availability. Some of its uses are in telecoms, banking, e-commerce, computer telephony and instant messaging. Erlang's runtime system has built-in support for concurrency, distribution and fault tolerance.

Most real-world Erlang applications are built using Erlang/OTP. OTP is set of Erlang libraries and design principles providing middleware to develop these systems. It includes its own distributed database, applications to interface towards other languages, debugging and release handling tools.

As an LFE programmer, you will have the ability and freedom to utilise Erlang and OTP libraries directly from a Lisp syntax.

In order to write LFE scripts, libraries, and applications, you will need to have Erlang installed. There are download links provided on the Erlang site, or you can use a tool such as kerl to download, build, and install multiple versions of Erlang on your system.

The rebar3 site also has a nice intro on getting started quickly with an Erlang installation.

About rebar3

Rebar3 is an Erlang tool that makes it easy to create, develop, and release Erlang libraries, applications, and systems in a repeatable manner.

Rebar3 will:

  • respect and enforce standard Erlang/OTP conventions for project structure so they are easily reusable by the community;
  • manage source dependencies and Erlang packages while ensuring repeatable builds;
  • handle build artefacts, paths, and libraries such that standard development tools can be used without a headache;
  • adapt to projects of all sizes on almost any platform;
  • treat documentation as a feature, and errors or lack of documentation as a bug.

Rebar3 is also a self-contained Erlang script. It is easy to distribute or embed directly in a project. Tasks or behaviours can be modified or expanded with a plugin system flexible enough that even other languages on the Erlang VM will use it as a build tool.

The rebar3 site provides some nice instructions on installing rebar3.

Going Plaid

Remember: this is a quick-start; it's going to be a blur of colour! You're not going to get very many details here, but you will get to jump into the LFE REPL and see a little code in action.1

The rest of this quick-start assumes that you've followed the links in the previous section and have installed both Erlang as well as rebar3, but to take things further, you'll need to do one more thing: set up the LFE plugin.

Each project you create with the LFE rebar3 plugin will generate a rebar.config file that automatically includes the plugin dependency, but that's only inside an LFE project. You need to bootstrap the LFE plugin by setting it up in your global rebar.config.

The rebar3 docs tell you this file is located at ~/.config/rebar3/rebar.config. To set this up, you can safely execute the following in a terminal, even if the file already exists:

mkdir -p ~/.config/rebar3/
touch ~/.config/rebar3/rebar.config

Then, in your preferred editor, open that file and add the entry for LFE rebar3 plugin. If that file is empty when you open it, then you can paste this whole thing in there:

{plugins, [
  {rebar3_lfe,
    {git, "https://github.com/lfe-rebar3/rebar3_lfe.git", {branch, "master"}}}
]}.

If you want to pin your project to a specific release of the plugin, you can view the list of released versions here:

  • https://github.com/lfe-rebar3/rebar3_lfe/tags

And then use tag (with the version) instead of branch:

{plugins, [
  {rebar3_lfe,
    {git, "https://github.com/lfe-rebar3/rebar3_lfe.git", {tag, "x.y.z"}}}
]}.

If your global rebar3 config file already has one or more plugins in it, then simply add a comma after the last one and paste the {rebar3_lfe ...} line from above (with no trailing comma!).

Next Stop

Ready for some LFE? Next you'll learn how to create a new LFE project with just one command ...


  1. For those that would enjoy a more in-depth introduction and would appreciate having the time to see the stars (and not just stunning plaid), you may be interested in checking out The LFE Tutorial.

Creating a New Project

A project? Already?! It sounds daunting, but it's easier than you might think. Open up a terminal window and do this in a directory of your choosing:

rebar3 new lfe-lib my-test-lib

It might take a minute or two to finish; here's what's happening:

  • rebar3 downloads the LFE plugin
  • Finds its dependencies, downloads those too
  • Compiles all of them (plugins and dependencies)
  • rebar3 then executes the new command, searches for (and finds!) the lfe-lib template
  • Creates the project files for the given template

As that last step executes, you will see the following output:

===> Writing my-test-lib/README.md
===> Writing my-test-lib/LICENSE
===> Writing my-test-lib/rebar.config
===> Writing my-test-lib/.gitignore
===> Writing my-test-lib/src/my-test-lib.lfe
===> Writing my-test-lib/src/my-test-lib.app.src

It's as simple as that! Your new project is ready to go :-)

Next Stop

You can taste it, can't you? That LFE flavour coming your way? Yup, you're right. You're going to be looking at LFE code next ...

Hitting the Code

It may not seem like it, but we're off to a pretty fast start. If you had to do everything we've done, manually, you'd have given up by now. Seriously.

(Okay, maybe not.)

Time to put the brakes on, though, 'cause you're gonna want to see this next part in slow motion.

REPL Me Up!

Make sure you've cded into your new LFE project directory, and then do this:

$ rebar3 lfe repl

This should give you something that looks like the following:

Erlang/OTP 23 [erts-11.0.2] [source] [64-bit] [smp:12:12] [ds:12:12:10] [async-threads:1] [hipe] [dtrace]

   ..-~.~_~---..
  (      \\     )    |   A Lisp-2+ on the Erlang VM
  |`-.._/_\\_.-':    |   Type (help) for usage info.
  |         g |_ \   |
  |        n    | |  |   Docs: http://docs.lfe.io/
  |       a    / /   |   Source: http://github.com/rvirding/lfe
   \     l    |_/    |
    \   r     /      |   LFE v1.3-dev (abort with ^G)
     `-E___.-'

lfe>

Try setting a variable:

> (set my-list (lists:seq 1 6))
(1 2 3 4 5 6)

Here are some operations using more functions from the built-in Erlang lists module:

> (* 2 (lists:sum my-list))
42
> (* 2 (lists:foldl (lambda (n acc) (+ n acc)) 0 my-list))
42

Let's turn that into a function:

> (defun my-sum (start stop)
    (let ((my-list (lists:seq start stop)))
      (* 2 (lists:foldl
             (lambda (n acc)
               (+ n acc))
             0 my-list))))
my-sum

And try it out!

> (my-sum 1 6)
42

Enough with the fancy REPL-play ... What about some real code? What does a project look like?

Sample Code

Well, you've already seen some! But here is the full, if minimal, module generated by the LFE rebar3 plugin:

(defmodule my-test-lib
  (export (my-fun 0)))

;;; -----------
;;; library API
;;; -----------

(defun my-fun ()
  'hello-world)

You'll note that the function we define has been exported via the export form in the module definition. The number after the function is the arity of that function (Erlang views functions of the same name but different arity as different functions, and LFE does the same).

In the REPL you will have access to this module and its one function. Try it out:

lfe> (my-test-lib:my-fun)
hello-world

Let's add to this module our new my-sum function from the REPL jam session in the previous section. In another terminal window (or text editor pane) open up the src/my-test-lib.lfe file and past the my-sum function.

When you're done, the entire file should look like this:

(defmodule my-test-lib
  (export (my-fun 0
          (my-sum 2))))

;;; -----------
;;; library API
;;; -----------

(defun my-fun ()
  'hello-world)

(defun my-sum (start stop)
  (let ((my-list (lists:seq start stop)))
    (* 2 (lists:foldl
            (lambda (n acc)
              (+ n acc))
            0 my-list))))

Then come back to the REPL sessions and compile the module with its new addition:

> (c "src/my-test-lib.lfe")
#(module my-test-lib)

And call the module functions:

> (my-test-lib:my-sum 1 6)
42
> (my-test-lib:my-sum 1 60)
3660
>

Here's something a little more involved you may enjoy, from the examples in the LFE source code:

(defun print-result ()
  (receive
    ((tuple pid msg)
      (io:format "Received message: '~s'~n" (list msg))
      (io:format "Sending message to process ~p ...~n" (list pid))
      (! pid (tuple msg))
      (print-result))))

(defun send-message (calling-pid msg)
  (let ((spawned-pid (spawn 'my-test-lib 'print-result ())))
    (! spawned-pid (tuple calling-pid msg))))

That bit of code demonstrates one of Erlang's core features in lovely Lisp syntax: message passing. When loaded into the REPL, that code can demonstrate bidirectional message passing between the LFE shell and a spawned process.

Want to give it a try? Add those two new functions to your module, and don't forget to update the export section, too! (note that one function has an arity of 0 and the other and arity of 2).

When you're done, your project module should look like this:

(defmodule my-test-lib
  (export (my-fun 0)
          (my-sum 2)
          (print-result 0)
          (send-message 2)))

;;; -----------
;;; library API
;;; -----------

(defun my-fun ()
  'hello-world)

(defun my-sum (start stop)
  (let ((my-list (lists:seq start stop)))
    (* 2 (lists:foldl
            (lambda (n acc)
              (+ n acc))
            0 my-list))))

(defun print-result ()
  (receive
    ((tuple pid msg)
      (io:format "Received message: '~s'~n" (list msg))
      (io:format "Sending message to process ~p ...~n" (list pid))
      (! pid (tuple msg))
      (print-result))))

(defun send-message (calling-pid msg)
  (let ((spawned-pid (spawn 'my-test-lib 'print-result ())))
    (! spawned-pid (tuple calling-pid msg))))

Message-Passing

We glossed over this in the previous section, but in LFE (and Erlang) you can compile on-the-fly in a REPL session. This is super-conventient when prototyping functionality for a new project where you want to use the REPL, but you also want the benefits of using a file, so you don't loose your work.

We made some changees to the sample code in the last section; let's compile it and take it for a spin:

> (c "src/my-test-lib.lfe")
#(module my-test-lib)

Next, let's send two messages to another Erlang process (in this case, we'll send it to our REPL process, (self):

> (my-test-lib:send-message (self) "And what does it say now?")
#(<0.26.0> "And what does it say now?")
Received message: 'And what does it say now?'
Sending message to process <0.26.0> ...
> (my-test-lib:send-message (self) "Mostly harmless.")
#(<0.26.0> "Mostly harmless.")
Received message: 'Mostly harmless.'
Sending message to process <0.26.0> ...

In the above calls, for each message sent we got a reply acknowledging the message (because the example was coded like that). But what about the receiver itself? What did it, our REPL process, see? We can flush the message queue in the REPL to find out.

What, what? Does each ...

Yup, every function in LFE (and Erlang, of course) has an inbox. You can see how many messages a give process has by looking at the process' info:

lfe> (erlang:process_info (self) 'message_queue_len)
#(message_queue_len 2)

And there you can see that our REPL process has two messages queued up in its inbox. Let's take a look!

> (c:flush)
Shell got {"And what does it say now?"}
Shell got {"Mostly harmless."}
ok

If you found this last bit interesting and want to step through a tutorial on Erlang's light-weight threads in more detail, you may enjoy this tutorial.

Next Stop

We did promise a bit more information, so we're going to do that next and then wrap up the quick start and point you in some directions for your next LFE adventures ...

Behind the Scenes

As we warned in the beginning, there's a lot going on behind the scenes: in rebar3 as well as LFE and Erlang (we haven't even touched OTP here ...!). This guide just gives you a quick taste of the LFE flavour :-) Before parting ways, though, there are some more bits we should share.

Some of those things are hinted at when just checking the current versions you are running using the LFE plugin's versions command (from a terminal where you have cded into your project directory):

rebar3 lfe versions
(#(apps (#(my-test-lib git)))
 #(languages
   (#(lfe "1.3-dev")
    #(erlang "23")
    #(emulator "11.0.2")
    #(driver_version "3.3")))
 #(tooling (#(rebar "3.10.0") #(rebar3_lfe "0.2.0"))))

To give a sense of what you'll encounter in the future: very often Erlang, LFE, and other BEAM language apps include more than just themselves when they are shipped. For instance, if you're in the REPL and you type (regs) you will see a list of applications that have been registered by name, currently running in support of the REPL. Usually, each app will have its own version. There is an LFE blog series on such things, showing you how to create and mess around with different types of LFE apps.

The LFE rebar3 plugin will also help you create OTP apps in LFE and perform other key tasks you may wish to integrate into your development workflow. You can learn more about those in the plugin's Command Reference

Next Stop

Where to go from here ...

Where Next?

We've mentioned the following resources so far:

But there are also these:

True mastery of LFE is not matter of syntax, though: it requires a deep knowledge of Erlang/OTP and how to best apply that knowledge. The Erlang site has links to great Erlang books you can read.

Feedback and Docs Bugs

If you would like to provide feedback about this guide, we would welcome the chance to improve the experience for everyone. Please create a ticket in the Github issue tracker. Be sure to give a full description so that we can best help you!