lfe_gen(3)

πŸ“¦ version 2.2.0, in alpine 3.24, nixos 26.05, nixpkgs unstable

πŸ’… lfe_gen β€” the glow-up generator

okay so bestie, welcome to lfe_gen(3) β€” that's Lisp Flavoured Erlang (LFE) dynamic code generator, and yes the manual name is literally in the title because we're classy like that ✨

πŸ¦‹ SYNOPSIS

This module hands you an experimental interface for dynamically generating modules. Experimental, babes. Like, it works, but it's giving "science fair project that slays" πŸ§ͺπŸ’–

πŸŽ€ DATA TYPES

  • sexpr() β€” an LFE s-expression. Basically a list structure. That's it, that's the whole vibe 🫢

✨ EXPORTS

Okay here's the full lineup, no skips, no cuts πŸ‘‘

  • new_module(Name) -> ModDef.
  • add_exports([[Name,Arity]], ModDef) -> ModDef.
  • add_imports([from,Module|Functions], ModDef) -> ModDef.
  • add_imports([rename,Module|Renames], ModDef) -> ModDef.
  • add_attribute(Attribute, ModDef) -> ModDef.
  • add_form(Form, ModDef) -> ModDef.
  • build_mod(ModDef) -> Forms.
  • compile_mod(Mod) -> CompRet

and where, bestie, where:

CompRet = BinRet | ErrRet
BinRet = {ok,ModuleName,Binary,Warnings}
ErrRet = {error,Errors,Warnings}

So what are these for? They let you incrementally build up a module piece by piece β€” like stacking bracelets, one at a time β€” and then at the very end you compile the whole thing with compile_mod/1. 🌈

The various components have exactly the same formats as they do when you're defining a module in a file. No new rules, no surprises, iykyk.

A simple module that defines one function a/0 could be defined like this:

M0 = lfe_gen:new_module(foo),
M1 = lfe_gen:add_exports([[a,0]], M0),
M2 = lfe_gen:add_form([defun,a,[],[quote,yes]], M1),
lfe_gen:compile_mod(M2)

slay. look at her go πŸ’…


πŸ”₯ EXAMPLE

okay this one is the main character fr fr. In this example we build a module of parameters, where each parameter has a number of features, and each feature has a value.

The setup: we create one function for each parameter, and the feature is the function's argument. Whatever feature you pass in, you get back its value. Cute, right? πŸ“

We are creating code equivalent to:

-module(Name).
-export([<param1>/1,...]).

<param1>(Feature) ->
    case Feature of
        <feature1> -> <value1>;
        ...
        _ -> erlang:error({unknown_feature,<param1>,Feature)
    end.
...

but β€” and this is the iconic part β€” we're generating it and compiling it directly in memory, without ever generating a text file. No files were harmed in the making of this module πŸ’€

We assume that we have collected the data and have it in the form:

Params = [{Parameter,[{Feature,Value}]}]

The equivalent LFE code which we will be generating is:

(defmodule Name
  (export (<param1> 1) (<param2> 1) ... ))

(defun <param1> (f)
  (case f
    ('<feature1> '<value1>)
    ...
    (f (: erlang error (tuple 'unknown_feature '<param1> f)))))
...

The following code builds and compiles a module from the parameter data:

make_module(Name, Params) ->
    Mod0 = lfe_gen:new_module(Name),
    Exps = lists:map(fun ({Param,_}) -> [Param,1] end, Params),
    Mod1 = lfe_gen:add_exports(Exps, Mod0),
    Mod2 = make_funcs(Params, Mod1),
    lfe_gen:compile_mod(Mod2).

make_funcs([{Param,Fs}|Ps], Mod) ->
    %% Define catch-all which generates more explicit exit value.
    CatchAll = [f,[':',erlang,error,
                   [tuple,unknown_feature,[quote,Param],f]]],
    %% Build case clauses
    Fold = fun ({Feature,Value}, Cls) ->
                   [[[quote,Feature],[quote,Value]]|Cls]
           end
    Cls = lists:foldr(Fold, [CatchAll], Params),
    %% Build function.
    Func = [defun,Param,[f],['case',f,Cls]],
    make_funcs(Ps, lfe_gen:add_form(Func, Mod));
make_funcs([], Mod) -> Mod.                     %All done

This module could be generated and then be loaded into the system by doing:

{ok,ModuleName,Binary,Warnings} = make_module(Name, Params),
code:load_binary(ModuleName, "nofile", Binary)

The second argument to code:load_binary/3 β€” here "nofile" β€” is irrelevant in this case. like, genuinely irrelevant, don't stress about it πŸ™ˆ


πŸ’€ ERROR INFORMATION

The ErrorInfo mentioned above is the standard ErrorInfo structure, the one returned from all IO modules. It has the following format:

{ErrorLine,Module,ErrorDescriptor}

A string describing the error is obtained with the following call:

apply(Module, format_error, ErrorDescriptor)

periodt. that's how you get the tea on what went wrong πŸ”₯


🌸 SEE ALSO

go read those too, bestie, they're literally right there 🫢


πŸ‘‘ AUTHORS

Robert Virding.


okay that's the whole page, no cap. every flag, every arg, every struct, every example β€” all present and accounted for πŸ’–

and before you close the tab: this iconic little number is dated 2008-2016, and it's page lfe_gen(3) of the manual, so go forth and generate modules dynamically. slay πŸ§šβ€β™€οΈβœ¨

πŸ“¦ where this page comes from