[erlang-questions] Pipe Operator in Erlang?

Fred Hebert mononcqc@REDACTED
Thu Jul 9 19:44:55 CEST 2015


On 07/09, José Valim wrote:
>I agree with the general feelings of the thread. F# relies on currying.
>Elixir relies on macros (it is actually closer to the thread operator found
>in some lisps).
>
>Also F# has most of its standard library expecting the "subject" as last
>argument (due to currying). Elixir defaults to the first argument. Erlang,
>for better or worse, has them mixed (see binary and lists modules).
>

I agree with this. The only workable form I could imagine could go 
something like this with macros, without major changes to the language:

pipe(Init, a(_, X), b(Y, _), c(_, _, 41.12)), where '_' gets replaced by 
the threaded in 'Init' state. The obvious problem is lack of 
composition:

pipe(Init, pipe(Init2, a(_))) where you can't know if a(_) refers to 
Init or Init2 at a glance without knowing precise evaluation rules.

Generally I haven't felt I missed this feature too much in Erlang. In 
the cases where it could really be nice, I resorted to using:

    pipe(Init, Funs) ->
        lists:foldl(fun(F, State) -> F(State) end, Init, Funs).

    pipe(Init,
         [fun(S) -> set(S, 230) end,
          fun(S) -> update(S) end,
          fun(S) -> output(S), S end]).

Obviously, one could easily come and swoop in with a macro:

    pipe(Init, $$,          % $$ is replaced by 'Init'
         set($$, 230),
         update($$),
         fun() -> output($$), $$ end)

The problem is that composition is not obvious:

    pipe(Init, $$,
         pipe($$, $_,
              set($_, 230),
              update($_),
              fun() -> output($$), $_ end))

In that case, the last 'output($$)' would try to print the literal '$$' 
value instead of the substituted one given underneath it all, it's 
equivalent to:

    pipe(Init, [fun(S) -> pipe(S, [...]) end]).

Woops! But what's cool? Add in a maybe pipe!

    maybe_pipe(Init, Funs) ->
        %% use throws and catches if you wanna go faster
        lists:foldl(fun(F, {ok, State}) -> F(State)
                    ;  (F, {error, Err}) -> {error, Err} end,
                    Init,
                    Funs).

And with the same set of parse transforms you can change:

    f(X0) ->
        case g(X0) of
            {ok, X1} ->
                case h(X1) of
                    {ok, X2} -> {ok, X2};
                    Err = {error, _} -> Err
                end;
            Err = {error, _} ->
                Err
        end.

Into:

    f(X0) ->
        maybe_pipe(X0, $$, g($$), h($$)).

Which translates to:

    f(X0) ->
        maybe_pipe(X0,
                   [fun(X) -> g(X) end,
                    fun(X) -> h(X) end]).

And all of this is doable today in library code for or from anyone, 
doesn't fundamentally change the shape of Erlang, although it will 
definitely be more confusing for newcomers or people not familiar with 
the concept.

All it needs is someone angry enough to do it. I don't believe (from 
doing experiments like Hubble[1]) that it's particularly hard.

Regards,
Fred.

[1]: https://github.com/ferd/hubble




More information about the erlang-questions mailing list