README.md

Pipe Macro
==========

This package provides an Erlang macro to mimic the Elixir pipe operator, but
with some additional capabilities compared to its Elixir counterpart.

Usage
-----

The main file is the header `include/pipe_macro.hrl` which defines the macro;
include it in any module where it will be used with the usual directive:

```erlang
-include_lib("pipe_macro/include/pipe_macro.hrl").
```

The macros defined here are `?PIPE` and `?PARG`--they work together to achieve
pipeline functionality similar to `|>` in Elixir.
The main difference is that while `|>` implicitly passes results, `?PARG` must
explicitly be used wherever a result is to be passed.
The basic form is:

```erlang
?PIPE(Expr1, Expr2, ..., ExprN)
```

where each `Expr` parameter is any valid Erlang expression. Any `Expr` _after_
the first can include any number of `?PARG`. Any `?PARG` used in the first
expression is an error.

> 💡 `?PIPE` overloads have been defined up to an arity of 10. If you want to
> use more than 10 expressions, simply add overloads in the header file
> following the established pattern.


All `?PARG` are replaced by the result of the previous expression.
For example:

```erlang
[1, 2, 3] = ?PIPE([1, [2], 3], lists:flatten(?PARG))
```

See [Examples](#examples) for more.

Examples
--------

### Passing results

The result of an expression must be explicitly passed with `?PARG` to use it in
the expression that follows:

```erlang
%% Same as lists:flatten([1, [2], 3])
[1, 2, 3] = ?PIPE([1, [2], 3], lists:flatten(?PARG)),

%% Same as lists:map(fun(X) -> X * 2 end, lists:flatten([1, [2], 3]))
[2, 4, 6] = ?PIPE([1, [2], 3],
                  lists:flatten(?PARG),
                  lists:map(fun(X) -> X * 2 end, ?PARG)),
```

If `?PARG` is _not_ used, the previous expression's result is ignored, and the
compiler will give a warning about an unused variable:

```erlang
%% This will cause a compiler warning due to no `?PARG' in the 2nd expression
[8, 10, 12] = ?PIPE([1, [2], 3], % this goes unused and triggers a warning
                    lists:flatten([4,[5],6]),
                    lists:map(fun(X) -> X * 2 end, ?PARG)), % uses flatten result
```

### Variables

Variables bound outside of the pipeline can be used in the expressions:

```erlang
%% Bound variables used in pipeline expressions
Me = pid_to_list(self()),
[$I,$\ ,$a,$m,$:|Me] = ?PIPE(Me, lists:append(["I am:", ?PARG])),
```

### Composing

As its arguments can be any valid expressions, `?PIPE` can also be used to
compose arbitrary terms and values:

```erlang
%% No func calls, just compose something
#{one := 1, 1 := one} = ?PIPE(one, #{?PARG => 1, 1 => ?PARG}),
```

Including function values:

```erlang
%% Create a nested anonymous function
Upcase_word = ?PIPE(fun(X) when $a =< X,  X =< $z -> X + $A - $a; (X) -> X end,
                    fun(X) -> lists:map(?PARG, X) end),

"ERLANG" = Upcase_word("Erlang"),

["I","LIKE","ERLANG"] = lists:map(Upcase_word, ["I","like","Erlang"]),
```

### Lazy calls

The last random example of `?PIPE` use is getting results from a lazy function.
This is based on the function `ints_from/1` in the `lazy` module of the
[Erlang programming example](https://www.erlang.org/docs/27/system/funs.html#infinite-lists).

To get a list of 5 results at a time, instead of writing something like:

```erlang
XX = ints_from(1),

First = hd(XX()),
Tail1 = tl(XX()),

Second = hd(Tail1()),
Tail2 = tl(Tail1()),

Third = hd(Tail2()),
Tail3 = tl(Tail2()),

Fourth = hd(Tail3()),
Tail4 = tl(Tail3()),

Fifth = hd(Tail4()),

[1, 2, 3, 4, 5] = [First, Second, Third, Fourth, Fifth],
```

Using `?PIPE` can eliminate the intermediate variables, at the cost of making
the code unreadable:

```erlang
YY = ints_from(1),

%% Complicated, and may cause blindness, but works the same
[1, 2, 3, 4, 5] =
  ?PIPE([tl(YY()), hd(YY())],
        [tl((hd(?PARG))()), hd((hd(?PARG))()) | tl(?PARG)],
        [tl((hd(?PARG))()), hd((hd(?PARG))()) | tl(?PARG)],
        [tl((hd(?PARG))()), hd((hd(?PARG))()) | tl(?PARG)],
        [tl((hd(?PARG))()), hd((hd(?PARG))()) | tl(?PARG)],
        lists:reverse(tl(?PARG))),
ok.
```