Skip to content

Instantly share code, notes, and snippets.

@eproxus
Last active February 2, 2023 12:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save eproxus/ff45250c5523974cdf573186563f4080 to your computer and use it in GitHub Desktop.
Save eproxus/ff45250c5523974cdf573186563f4080 to your computer and use it in GitHub Desktop.
Extending the Erlang Shell

Extending the Erlang Shell

Erlang makes it possible to extend the Erlang shell with your own built-in functions using the special module user_default. We can use this to add functions to the shell, and even add custom libraries to the code path for all Erlang shells.

How To Setup

  1. Start by creating a file called .erlang in your home directory. Any Erlang expression ending with a dot in this file will be executed when the Erlang shell starts
  2. Create the directory ~/.erlang.d:
    $ mkdir ~/.erlang.d
    (The path can be any path you like, just remember to edit all paths in this instruction to match)
  3. Create a file called user_default.erl in that directory
  4. Compile it:
    $ cd ~/.erlang.d
    $ erlc user_default.erl

Use in Rebar Shells

  1. Add a file called rebar.escript in ~/.erlang.d
  2. Edit your global ~/.config/rebar3/rebar.config and add a shell script file directive with the full path to that escript (replace /path/to/home with the actual path to your home folder):
    {shell, [{script_file, "/path/to/home/.erlang.d/rebar.escript"}]}.

Load Custom Libraries

The example user_default.erl file in this Gist includes on_load functionality to add any cloned libraries inside ~/.erlang.d to the Erlang path. To for example enable Recon in every Erlang shell, you can just clone it and compile it:

$ cd ~/.erlang.d
$ git clone https://github.com/ferd/recond
Cloning into 'recon'...
remote: Enumerating objects: 1366, done.
remote: Counting objects: 100% (88/88), done.
remote: Compressing objects: 100% (70/70), done.
remote: Total 1366 (delta 15), reused 70 (delta 13), pack-reused 1278
Receiving objects: 100% (1366/1366), 1.11 MiB | 2.25 MiB/s, done.
Resolving deltas: 100% (771/771), done.
$ cd recon
$ rebar3 compile
===> Fetching rebar3_ex_doc v0.2.16
===> Analyzing applications...
===> Compiling rebar3_ex_doc
===> Verifying dependencies...
===> Analyzing applications...
===> Compiling recon

From this point on, Recon will be available in the Erlang code path for all Erlang shells.

Caveats

⚠️ If you are using multiple Erlang versions (e.g. with asdf, Kerl or rtx), remember to compile the user defaults module and your cloned libraries with the lowest Erlang version you are going to use. If not, you will get errors loading newer versions of modules in older Erlang versions.

code:load_abs(filename:join(os:getenv("HOME"), ".erlang.d/user_default")).
#!/usr/bin/env escript
main(_) ->
code:load_abs(filename:join(os:getenv("HOME"), ".erlang.d/user_default")).
-module(user_default).
% API
-export([dbg/0]).
-export([tp/1]).
-export([tp/2]).
-export([tpl/1]).
-export([tpl/2]).
-export([ctp/0]).
-export([bench/2]).
-export([bench/3]).
-export([bench/4]).
%--- Startup -------------------------------------------------------------------
-on_load(on_load/0).
on_load() ->
% Add all libraries cloned to ~/.erlang.d to the Erlang path
Dir = filename:join(os:getenv("HOME"), ".erlang.d"),
code:add_pathsz([
filename:join([Dir, P, "ebin"])
||
P <- filelib:wildcard("*/_build/default/lib/[a-zA-Z]*", Dir)
]),
% Put any other code here you want to be executed at shell startup
ok.
%--- API -----------------------------------------------------------------------
dbg() ->
dbg:tracer(),
dbg:p(all, call).
tp(M) ->
dbg(),
dbg:tp(M, x).
tp(M, F) ->
dbg(),
dbg:tp(M, F, x).
tpl(M) ->
dbg(),
dbg:tpl(M, x).
tpl(M, F) ->
dbg(),
dbg:tpl(M, F, x).
ctp() ->
dbg(),
dbg:ctp().
bench(Fun, N) -> bench(Fun, N, 0.03).
bench(Fun, N, Warmups) when is_number(N) ->
_ = run(Fun, warmups(N, Warmups)),
Results = run(Fun, N),
#{median => median(Results), average => average(Results)};
bench(FunA, FunB, N) ->
bench(FunA, FunB, N, 0.03).
bench(FunA, FunB, N, Warmups) ->
#{median := MedianA, average := AverageA} = bench(FunA, N, Warmups),
#{median := MedianB, average := AverageB} = bench(FunB, N, Warmups),
#{
median => {MedianA, MedianB, rel(MedianA, MedianB)},
average => {AverageA, AverageB, rel(AverageA, AverageB)}
}.
%--- Internal ------------------------------------------------------------------
warmups(N, Warmups) when is_float(Warmups), Warmups >= 0.0, Warmups =< 1.0 ->
max(round(N * Warmups), 1);
warmups(_N, Warmups) when is_integer(Warmups), Warmups >= 0 ->
Warmups;
warmups(_N, Warmups) ->
error({invalid_warmups, Warmups}).
run(Fun, N) ->
Runs = lists:seq(1, N),
[begin {T, _} = timer:tc(Fun), {I, T} end || I <- Runs].
median(Results) ->
lists:nth(length(Results) div 2, lists:sort([R || {_, R} <- Results])).
average(Results) ->
lists:sum([R || {_, R} <- Results]) / length(Results).
rel(_A, 0) -> inf;
rel(A, B) -> B / A.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment