Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Complete Transformation Example

Let's trace a complete example from LFE source through to BEAM bytecode, showing every intermediate representation.

Source code (factorial.lfe):

(defmodule factorial
  (export (fac 1)))

(defun fac (n)
  (fac-helper n 1))

(defun fac-helper
  ([0 acc] acc)
  ([n acc] (when (> n 0))
   (fac-helper (- n 1) (* n acc))))

Stage 1: Tokens (abbreviated):

[{'(', 1}, {symbol, 1, defmodule}, {symbol, 1, factorial},
 {'(', 2}, {symbol, 2, export}, {'(', 2}, {symbol, 2, fac}, {number, 2, 1}, ...

Stage 2: S-expressions:

[defmodule, factorial,
  [export, [fac, 1]]]

[defun, fac, [n],
  ['fac-helper', n, 1]]

[defun, 'fac-helper',
  [[0, acc], acc],
  [[n, acc], [when, ['>', n, 0]],
    ['fac-helper', ['-', n, 1], ['*', n, acc]]]]

Stage 3: After Macro Expansion (lfe_macro.erl):

The macros defmodule and defun expand to core forms:

;; defmodule expands to define-module
['define-module', factorial,
  [],                          % Metadata
  [[export, [fac, 1]]]]       % Attributes

;; defun expands to define-function
['define-function', fac,
  [],                          % Metadata
  [lambda, [n],                % Single-clause function
    ['fac-helper', n, 1]]]

;; defun with multiple clauses expands to match-lambda
['define-function', 'fac-helper',
  [],                          % Metadata
  ['match-lambda',             % Multi-clause function
    [[0, acc], acc],
    [[n, acc], [when, ['>', n, 0]],
      ['fac-helper', ['-', n, 1], ['*', n, acc]]]]]

Stage 4: After Linting (lfe_lint.erl):

Forms are validated (variables bound, exports match definitions, etc.). No transformation, just error checking.

Stage 5: After Code Generation (lfe_codegen.erl):

Module structure is built, ready for translation:

%% Module attributes
{module, factorial}
{exports, [{fac, 1}]}

%% Function definitions (still LFE forms, but organized)
{function, fac, 1,
  [lambda, [n], ['fac-helper', n, 1]]}

{function, 'fac-helper', 2,
  ['match-lambda',
    [[0, acc], acc],
    [[n, acc], [when, ['>', n, 0]],
      ['fac-helper', ['-', n, 1], ['*', n, acc]]]]}

Stage 6: After Translation to Erlang AST (lfe_translate.erl):

This is where LFE becomes Erlang Abstract Format:

%% Module attribute
{attribute, 1, module, factorial}

%% Export attribute
{attribute, 2, export, [{fac, 1}]}

%% Function: fac/1
{function, 4, fac, 1,
  [{clause, 4,
    [{var, 4, 'N'}],          % Parameters
    [],                        % Guards
    [{call, 5,                % Body
       {atom, 5, 'fac-helper'},
       [{var, 5, 'N'}, {integer, 5, 1}]}]}]}

%% Function: fac-helper/2
{function, 7, 'fac-helper', 2,
  [%% Clause 1: base case
   {clause, 8,
    [{integer, 8, 0}, {var, 8, 'Acc'}],  % Pattern
    [],                                    % No guard
    [{var, 8, 'Acc'}]},                   % Body

   %% Clause 2: recursive case
   {clause, 9,
    [{var, 9, 'N'}, {var, 9, 'Acc'}],    % Pattern
    [[{op, 9, '>', {var, 9, 'N'}, {integer, 9, 0}}]],  % Guard
    [{call, 10,                           % Body: recursive call
       {atom, 10, 'fac-helper'},
       [{op, 10, '-', {var, 10, 'N'}, {integer, 10, 1}},
        {op, 10, '*', {var, 10, 'N'}, {var, 10, 'Acc'}}]}]}]}

Stage 7: Core Erlang (intermediate representation before BEAM):

The Erlang compiler (compile:forms/2) translates Abstract Format to Core Erlang:

module 'factorial' ['fac'/1, 'module_info'/0, 'module_info'/1]
  attributes []

'fac'/1 =
  fun (N) ->
    call 'fac-helper'(N, 1)

'fac-helper'/2 =
  fun (0, Acc) -> Acc
  fun (N, Acc) when call 'erlang':'>'(N, 0) ->
    let <_2> = call 'erlang':'-'(N, 1)
        <_3> = call 'erlang':'*'(N, Acc)
    in call 'fac-helper'(_2, _3)

Stage 8: BEAM Bytecode:

Core Erlang is compiled to BEAM assembly, then to bytecode. The .beam file contains:

  • Module name: factorial
  • Exports: [{fac,1}]
  • Attributes: []
  • Compiled code: native BEAM instructions
  • Metadata: line numbers, debug info

Key observation: The final BEAM bytecode for this LFE module is identical to what would be produced from equivalent Erlang code. This is LFE's zero-overhead interoperability.