Collection of Erlang parse transforms

Version: 0.2.2-12-gc1e0b6f

Authors: : Serge Aleynikov (saleyn(at)gmail.com).

Collection of Erlang Parse Transforms

License: MIT License

This library includes useful parse transforms including Elixir-like pipeline operator for cascading function calls.

Content

ModuleDescription
erlpipeElixir-like pipeline for Erlang
mapreduceMapReduce: Fold Comprehension and FoldMap Comprehension
iifTernary if function including iif/3, iif/4, ife/3, ife/4 parse transforms
strStringification functions including str/1, str/2, and throw/2 parse transforms

Erlang Pipeline (erlpipe)

Inspired by the Elixir's |> pipeline operator. This transform makes code with cascading function calls much more readable by using the / as the pipeline operator. In the LHS / RHS / ... Last. notation, the result of evaluation of the LHS expression is passed as an argument to the RHS expression. This process continues until the Last expression is evaluated. The head element of the pipeline must be either a term to which the arithmetic division / operator cannot apply (i.e. not integers, floats, functions), or if you need to pass integer(s) or float(s), wrap them in a list brackets.

It transforms code from:

test1(Arg1, Arg2, Arg3) ->
  [Arg1, Arg2]                                  %% Variables must be enclosed in `[...]`
  / fun1
  / mod:fun2
  / fun3()                                      %% In function calls parenthesis are optional
  / fun4(Arg3, _)
  / io_lib:format("~p\n", [_])
  / fun6([1,2,3], _, other_param)
  / fun7.
print(L) when is_list(L) ->
  [lists:split(3, L)]                           %% Function calls must be enclosed in `[...]`
  / element(1, _)
  / binary_to_list
  / io:format("~s\n", [_]).
test2() ->
  3       = abc        / atom_to_list / length, %% Atoms    can be passed to '/' as is
  3       = "abc"      / length,                %% Strings  can be passed to '/' as is
  "abc"   = <<"abc">>  / binary_to_list,        %% Binaries can be passed to '/' as is
  "1,2,3" = {$1,$2,$3} / tuple_to_list          %% Tuples   can be passed to '/' as is
                       / [[I] || I <- _]
                       / string:join(_, ","),
  "abc\n" = "abc"      / (_ ++ "\n"),           %% Can use operators on the right hand side
  2.0     = 4.0        / max(1.0, 2.0),         %% Expressions with lhs floats are unmodified
  2       = 4          / max(1, 2).             %% Expressions with lhs integers are unmodified

to the following equivalent:

test1(Arg1, Arg2, Arg3) ->
  fun7(fun6([1,2,3],
            io_lib:format("~p\n", [fun4(Arg3, fun3(mod2:fun2(fun1(Arg1, Arg2))))]),
            other_param)).
print(L) when is_list(L) ->
  io:format("~s\n", [binary_to_list(element(1, lists:split(3, L)))]).
test2() ->
  3       = length(atom_to_list(abc)),
  3       = length("abc"),
  "abc"   = binary_to_list(<<"abc">>),
  "1,2,3" = string:join([[I] || I <- tuple_to_list({$1,$2,$3})], ","),
  "abc\n" = "abc" ++ "\n",
  2.0     = 4.0 / max(1.0, 2.0),
  2       = 4   / max(1, 2).

Similar attempts to tackle this pipeline transform have been done by other developers:

Yet, we subjectively believe that the choice of syntax in this implementation of transform is more succinct and elegant, and doesn't attempt to modify the meaning of the / operator for arithmetic LHS types (i.e. integers and floats).

Map-Reduce: Fold and MapFold Comprehensions (mapreduce)

Fold Comprehension

To invoke the fold comprehension transform include the initial state assignment into a comprehension that returns a non-tuple expression:

[S+I || S = 1, I <- L].
 ^^^    ^^^^^

In this example the S variable gets assigned the initial state 1, and the S+I expression represents the body of the fold function that is passed the iteration variable I and the state variable S:

lists:foldl(fun(I, S) -> S+I end, 1, L).

MapFold Comprehension

To invoke the mapfold comprehension transform include the initial state assignment into a comprehension, and return a tuple expression:

[{I, S+I} || S = 1, I <- L].
 ^^^^^^^^    ^^^^^

In this example the S variable gets assigned the initial state 1, and the {I, S+I} two-elements tuple expression represents the body of the fold function that is passed the iteration variable I and the state variable S:

lists:mapfoldl(fun(I, S) -> {I, S+I} end, 1, L).

Ternary if (iif)

This transform improves the code readability for cases that involve simple conditional tests. E.g.:

iif(tuple_size(T) == 3, good, bad).
iif(some_fun(A), match, ok, error).
nvl(L, undefined).
nvl(L, nil, hd(L))

are transformed to:

case tuple_size(T) == 3 of
  true      -> good;
  _         -> bad
end.
case some_fun(A) of
  match     -> ok;
  nomatch   -> error
end.
case L of
  []        -> undefined;
  false     -> undefined;
  undefined -> undefined;
  _         -> L
end.
case L of
  []        -> nil;
  false     -> nil;
  undefined -> nil;
  _         -> hd(L)
end.

String transforms (str)

This module implements a transform to stringify an Erlang term.

Dowloading

Building and Using

$ make

To use the transforms, compile your module with the +'{parse_transform, Module}' command-line option, or include -compile({parse_transform, Module}). in your source code, where Module is one of the transform modules implemented in this project.

To use all transforms implemented by the etran application, compile your module with this command-line option: +'{parse_transform, etran}'.

erlc +debug_info +'{parse_transform, etran}' -o ebin YourModule.erl

If you are using rebar3 to build your project, than add to rebar.config:

{erl_opts, [debug_info, {parse_transform, etran}]}.