Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up

Erlang Parse Transforms Including Fold (MapReduce) comprehension, Elixir-like Pipeline, and default function arguments

License

NotificationsYou must be signed in to change notification settings

saleyn/etran

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Author: Serge Aleynikov <saleyn(at)gmail.com>

License: MIT License

buildHex.pmHex.pm

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

Content

ModuleDescription
defargSupport default argument values in Erlang functions
erlpipeElixir-like pipe operator for Erlang
listcompFold Comprehension and Indexed List Comprehension
iifTernary if function includingiif/3,iif/4,nvl/2,nvl/3 parse transforms
strStringification functions includingstr/1,str/2, andthrow/2 parse transforms

defarg: Support default argument values in Erlang functions

Presently the Erlang syntax doesn't allow function arguments to have defaultparameters. Consequently a developer needs to replicate the functiondefinition multiple times passing constant defaults to some parameters offunctions.

This parse transform addresses this shortcoming by extending the syntaxof function definitions at the top level in a module to have a defaultexpression such that forA / Default argument theDefault will beused if the function is called in code without that argument.

Though it might seem more intuitive for programmers coming from otherlanguages to use the assignment operator= for defining default arguments,using that operator would change the current meaning of pattern matching ofarguments in function calls (i.e.test(A=10) is presently a valid expression).Therefore we chose the/ operator for declaring default arguments becauseit has no valid meaning when applied in declaration of function arguments,and presently without thedefarg transform, using this operator(e.g.test(A / 10) -> ...) would result in a syntax error detected by thecompiler.

-export([t/2]).test(A/10,B/20)->A+B.

The code above is transformed to:

-export([t/2]).-export([t/0,t/1]).test()->test(10);test(A)->test(A,20);test(A,B)->A+B.

The arguments with default values must be at the end of the argument list:

test(A,B,C/1)->%% This is valid  ...test(A/1,B,C)->%% This is invalid  ...

NOTE: The default arguments should be constant expressions. Function calls in defaultarguments are not supported!

test(A/erlang:timestamp())->%% !!! Bad syntax  ...

erlpipe: Erlang Pipe Operator

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

It transforms code from:

print(L)whenis_list(L)->  [3,L]%% Multiple items in a list become arguments to the first function/lists:split%% In Module:Function calls parenthesis are optional/element(1,_)%% '_' is the placeholder for the return value of a previous call/binary_to_list/io:format("~s\n", [_]).test1(Arg1,Arg2,Arg3)->  [Arg1,Arg2]%% Arguments must be enclosed in `[...]`/fun1%% In function calls parenthesis are optional/mod:fun2/fun3()/fun4(Arg3,_)%% '_' is the placeholder for the return value of a previous call/funff/1%% Inplace function references are supported/funerlang:length/1%% Inplace Mod:Fun/Arity function references are supported/fun(I) ->Iend%% This lambda will be evaluated as: (fun(I) -> I end)(_)/io_lib:format("~p\n", [_])/fun6([1,2,3],_,other_param)/fun7.test2()->% Result = Argument   / Function3=abc/atom_to_list/length,%% Atoms    can be passed to '/' as is3="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<-_]%% The '_' placeholder is replaced by the return of tuple_to_list/1/string:join(","),%% Here a call to string:join/2 is made"1"= [min(1,2)]/integer_to_list,%% Function calls, integer and float value"1"= [1]/integer_to_list,%% arguments must be enclosed in a list."1.0"= [1.0]/float_to_list([{decimals,1}]),"abc\n"="abc"/ (_++"\n"),%% Can use operators on the right hand side2.0=4.0/max(1.0,2.0),%% Expressions with lhs floats are unmodified2=4/max(1,2).%% Expressions with lhs integers are unmodifiedtest3()->A=10,B=5,2=A/B,%% LHS variables (e.g. A) are not affected by the transform2.0=10/5,%% Arithmetic division for integers, floats, variables is unmodified2.0=A/5,%% (ditto)5=max(A,B)/2.%% Use of division on LHS function calls is unaffected by the transform

to the following equivalent:

test1(Arg1,Arg2,Arg3)->fun7(fun6([1,2,3],io_lib:format("~p\n", [              (fun(I) ->Iend)(erlang:length(ff(fun4(Arg3,fun3(mod2:fun2(fun1(Arg1,Arg2)))))))]),other_param)).print(L)whenis_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})],","),"1"=integer_to_list(min(1,2)),"1"=integer_to_list(1),"1.0"=float_to_list(1.0, [{decimals,1}]),"abc\n"="abc"++"\n",2.0=4.0/max(1.0,2.0),2=4/max(1,2).

Similarly to Elixir, a specialtap/2 function is implemented, whichpasses the given argument to an anonymous function, returning the argumentitself. The following:

f(A)->A+1....test_tap()->  [10]/tap(f)/tap(funf/1)/tap(fun(I) ->I+1end).

is equivalent to:

...test_tap()->beginf(10),beginf(10),begin        (fun(I) ->I+1end)(10),10endendend.

Some 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 transformis more succinct and elegant, and doesn't attempt to modify the meaning of the/ operatorfor arithmetic LHS types (i.e. integers, floats, variables, and function calls).

Why didn't we use|> operator instead of/ to make it equivalent to Elixir?Parse transforms are applied only after the Erlang source code gets parsed to the ASTrepresentation, which must be in valid Erlang syntax. The|> operator is not known tothe Erlang parser, and therefore, using it would result in the compile-time error. Wehad to select an operator that the Erlang parser would be happy with, and/ was our choicebecause visually it resembles the pipe| character more than the other operators.

listcomp: Fold and Indexed List Comprehensions

Indexed List Comprehension

Occasionally the body of a list comprehension needs to know the indexof the current item in the fold. Consider this example:

[{1,10}, {2,20}]=element(1,lists:foldmapl(fun(I,N) -> {{N,I},N+1}end,1, [10,20])).

Here theN variable is tracking the index of the current itemI in the list.While the same result in this specific case can be achieved withlists:zip(lists:seq(1,2), [10,20]), in a more general case, there is no way to havean item counter propagated with the current list comprehension syntax.

TheIndexed List Comprehension accomplishes just that through the use of an unassignedvariable immediately to the right of the|| operator:

  [{Idx,I} ||Idx,I<-L].%              ^^^%               |%               +--- This variable becomes the index counter

Example:

[{1,10}, {2,20}]= [{Idx,I} ||Idx,I<- [10,20]].

Fold Comprehension

To invoke the fold comprehension transform include the initial stateassignment into a list comprehension:

  [S+I ||S=1,I<-L].%  ^^^    ^^^^^%   |       |%   |       +--- State variable bound to the initial value%   +----------- The body of the foldl function

In this example theS variable gets assigned the initial state1, andtheS+I expression represents the body of the fold function thatis passed the iteration variableI and the state variableS:

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

A fold comprehension can be combined with the indexed list comprehensionby using this syntax:

  [do(Idx,S+I) ||Idx,S=10,I<-L].%  ^^^^^^^^^^^^    ^^^  ^^^^^^%       |           |     |%       |           |     +--- State variable bound to the initial value (e.g. 10)%       |           +--------- The index variable bound to the initial value of 1%       +--------------------- The body of the foldl function can use Idx and S

This code is transformed to:

element(2,lists:foldl(fun(I, {Idx,S}) -> {Idx+1,do(Idx,S+I)}end, {1,10},L)).

Example:

33= [S+Idx*I ||Idx,S=1,I<- [10,20]],30= [print(Idx,I,S) ||Idx,S=0,I<- [10,20]].% Prints:%   Item#1 running sum: 10%   Item#2 running sum: 30print(Idx,I,S)->Res=S+I,io:format("Item#~w running sum:~w\n", [Idx,Res]),Res.

iif: Ternary and quaternary if

This transform improves the code readability for cases that involve simple conditionalif/then/else tests in the formiif(Condition, Then, Else). Since this is a parsetransform, theThen andElse expressions are evaluatedonly if theConditionevaluates totrue orfalse respectively.

E.g.:

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

are transformed to:

casetuple_size(T)==3oftrue      ->good;_         ->badend.casesome_fun(A)ofmatch     ->ok;nomatch   ->errorend.caseLof  []        ->undefined;false     ->undefined;undefined ->undefined;_         ->Lend.caseLof  []        ->nil;false     ->nil;undefined ->nil;_         ->hd(L)end.

str: String transforms

This module implements a transform to stringify an Erlang term.

  • str(Term) is equivalent tolists:flatten(io_lib:format("~p", [Term])) forterms that are not integers, floats, atoms, binaries and lists.Integers, atoms, and binaries are converted to string using*_to_list/1functions. Floats are converted usingfloat_to_list/2 where the secondargument is controled bystr:set_float_fmt/1 andstr:reset_float_fmt/0calls. Lists are converted to string usinglists:flatten(io_lib:format("~s", [Term])) and if that fails, then usinglists:flatten(io_lib:format("~p", [Term])) format.
  • str(Fmt, Args) is equivalent tolists:flatten(io_lib:format(Fmt, Args)).
  • bin(Fmt, Args) is equivalent tolist_to_binary(lists:flatten(io_lib:format(Fmt, Args))).
  • throw(Fmt,Args) is equivalent tothrow(list_to_binary(io_lib:format(Fmt, Args))).
  • error(Fmt,Args) is equivalent toerror(list_to_binary(io_lib:format(Fmt, Args))).

Two other shorthand transforms are optionally supported:

  • b2l(Binary) is equivalent tobinary_to_list(Binary) (enabled by giving{d,str_b2l})compilation option.
  • i2l(Integer) is equivalent tointeger_to_list(Binary) (enabled by giving{d,str_i2l})compilation option.

E.g.:

erlc +debug_info -Dstr_b2l -Dstr_i2l +'{parse_transform, str}' -o ebin your_module.erl

Dowloading

Building and Using

$ make

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

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

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

If you are usingrebar3 to build your project, then add torebar.config:

{deps, [{etran, "0.5.1"}]}.{erl_opts, [debug_info, {parse_transform, etran}]}.

About

Erlang Parse Transforms Including Fold (MapReduce) comprehension, Elixir-like Pipeline, and default function arguments

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

[8]ページ先頭

©2009-2025 Movatter.jp