[erlang-questions] Versioned variable names

Paul Mineiro <>
Tue Jun 9 19:45:30 CEST 2009


I took Ulf's ct_expand parse transform and modified it to provide let like
behaviour.  Something like

--------
-module (testlyt).
-compile ({ parse_transform, lyt }).
-compile (export_all).

g (X) -> X + 1.
h (X) -> X + 2.
l (X) -> X + 3.
m (X) -> X + 4.

f (X) ->  lyt:lyt (X = g (X), begin
    lyt:lyt (X = h (X), begin
      lyt:lyt (X = l (X), begin
        m (X)
      end)
    end)
  end).
--------

should work (let is a reserved keyword so i used lyt).  It produces
output like:

% erl -noshell -noinput -eval 'io:format ("~p~n", [ testlyt:f (1) ]).' -s
erlang halt
11

The transform is attached.

Cheers,

-- p


On Tue, 9 Jun 2009, Mikael Pettersson wrote:

> Attila Rajmund Nohl writes:
>  > Hello!
>  >
>  > I think there wasn't any grumbling this month about the immutable
>  > local variables in Erlang, so here's real world code I've found just
>  > today:
>  >
>  >     % Take away underscore and replace with hyphen
>  >     MO1 = re:replace(MO, "_", "-", [{return, list}, global]),
>  >     MO2 = toupper(MO1),
>  >     % Replace zeros
>  >     MO3 = re:replace(MO2, "RX0", "RXO", [{return, list}, global]),
>  >     % Insert hyphen if missing
>  >     MO5 = case re:run(MO3, "-", [{capture, none}]) of
>  > 	      nomatch ->
>  > 		  insert_hyphen(MO3);
>  > 	      match ->
>  > 		  MO3
>  > 	  end,
>  >
>  > I think it's fairly clumsy to use MOx (MO for managed object) in the
>  > code. MO4 was removed during the regexp->re refactoring step. How to
>  > eliminate the versioned variable names? The
>  > MOAfterUnderscoresWereReplaced, UpperCaseMO, MOAfterRX0WasReplaced,
>  > etc. variablenames are really ugly. It used to use regexp, so at that
>  > point it wasn't possible to easily nest the whole into one call, but
>  > that would be still ugly. So any other ideas?
>
> This has nothing to do with mutable variables (values or
> bindings), and everything to do with Erlang's unorthodox
> (for a functional language) scoping rules and semantics
> for "Var = Expr".
>
> Languages using LET for local bindings tend to allow you
> to re-bind already-bound variables in nested scopes. For
> instance, in Standard ML I would often write:
>
> fun f (..) =
>   let state = init()
>       state = stage1(state)
>       state = finish(state)
>   in
>     ... state ...
>   end
>
> and similarly in Scheme using LET*.
>
> In Erlang you can't do this, so you're stuck with:
> - numbered variables (ugly but often reasonably practical)
> - function composition like f(x) -> a(b(c(x))), but that
>   quickly gets unreadable
> - using a separate function for each stage and tailcall
>   between the stages:
> 	  f() -> g(init()).
> 	  g(X) -> h(update(X)).
> 	  h(X) ->
> 	    NewX = if ... -> update2(X); true -> X end,
> 	    i(NewX).
> 	  i(X) ->
>   this doesn't entirely eliminate the variable naming problem,
>   but it does limit it to say two versions per function body,
>   which is manageable (I use this approach quite a lot in the
>   HiPE compiler backends when translating generic intermediate
>   code to architecture specific code)
> - foldl (as another poster suggested), which is essentially
>   equivalent to function composition or using tailcalls between
>   per-stage functions, except it's expressed in different syntax
>
> ________________________________________________________________
> erlang-questions mailing list. See http://www.erlang.org/faq.html
> erlang-questions (at) erlang.org
>
>

Well for these men if they succeed; well also, though not so well, if
they fail, given only that they have nobly ventured, and have put forth
all their heart and strength.

        -- Theodore Roosevelt
-------------- next part --------------
-module (testlyt).
-compile ({ parse_transform, lyt }).
-compile (export_all).

g (X) -> X + 1.
h (X) -> X + 2.
l (X) -> X + 3.
m (X) -> X + 4.

f (X) ->
  lyt:lyt (X = g (X), begin
    lyt:lyt (X = h (X), begin
      lyt:lyt (X = l (X), begin
        m (X)
      end)
    end)
  end).
-------------- next part --------------
%%% The contents of this file are subject to the Erlang Public License,
%%% Version 1.0, (the "License"); you may not use this file except in
%%% compliance with the License. You may obtain a copy of the License at
%%% http://www.erlang.org/license/EPL1_0.txt
%%%
%%% Software distributed under the License is distributed on an "AS IS"
%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%%% the License for the specific language governing rights and limitations
%%% under the License.
%%%
%%% The Original Code is ___
%%%
%%% The Initial Developer of the Original Code is Ericsson Telecom
%%% AB. Portions created by Ericsson are Copyright (C), 1998, Ericsson
%%% Telecom AB. All Rights Reserved.
%%%
%%% Contributor(s): ______________________________________.
%%%
%%% ------------------------------------------------------------------
-module(lyt).
-id('').
-vsn('').
-date('2006-08-22').
-author('').

%%% This module is a quick hack to allow for compile-time expansion of
%%% complex expressions. To trigger the expansion, wrap the expression
%%% inside a call to lyt:lyt/1.
%%%
%%% A simple example:
%%%
%%% f() ->
%%%     lyt:lyt(
%%%       ordsets:from_list([this, is, a, list, 'of', atoms])
%%%      ).
%%%
%%% The parse_transform evaluates this into:
%%%
%%% f() -> [a, atoms, is, list, 'of', this].
%%%


-export([function/4,
	 format_error/1]).
-export([parse_transform/2]).



-define(ERROR(R, T, F, I),
	begin
	    rpt_error(R, T, F, I),
	    throw({error,erl_syntax:get_pos(
			   proplists:get_value(form,I)),{unknown,R}})
	end).

-import(erl_syntax, [clause/3,
		     clause_patterns/1,
		     clause_body/1,
		     clause_guard/1,
		     match_expr/2,
		     function_clauses/1,
		     get_pos/1,
		     add_ann/2,
		     get_ann/1]).

%%% ==================================================================
%%% This is the custom code that's been added. The rest of the module
%%% is 'library code' (or should be...)
%%% Any expression wrapped inside a call to lyt:lyt(Var = Value, Expr) is
%%% replaced at compile-time with 
%%% (fun (Var) -> Expr end) (Value)
%%%
parse_transform(Forms, Options) ->
    function({?MODULE, lyt, 2},
	     fun(Form, _Context) ->
		     case erl_syntax:application_arguments(Form) of
			 [Assign, Expr] ->
                           { match, Line, { var, _, Var }, Val } =
                             erl_syntax:revert (Assign),

                           { call,
                             Line,
                             { 'fun', 
                               Line,
                               { clauses,
                                 [ { clause, 
                                     Line,
                                     [ { var, Line, Var } ],
                                     [],
                                     [ erl_syntax:revert (Expr) ] 
                                   }
                                 ]
                               }
                             },
                             [ Val ]
                           };
			 _Args ->
			     erlang:error(illegal_form)
		     end
	     end, Forms, Options).

%%% End custom code.
%%% ==================================================================


%%% API: function({Module, Function, Arity}, Fun, Forms, Options) ->
%%%         NewForms
%%%
%%% Forms and Options are the arguments passed to the parse_transform/2
%%% function.
%%% {Module, Function, Arity} is the function call to transform
%%% Fun(Form, Context) -> NewForm is the fun provided by the caller.
%%%
%%% Context is a property list, containing the following properties:
%%% - {file, Filename}
%%% - {module, ModuleName}
%%% - {function, FunctionName}       % name of the enclosing function
%%% - {arity, Arity :: integer()}    % arity of same
%%% - {var_names, Vars :: [atom()]}  % generated variables binding the
%%%                                  % function arguments.
%%%                                  % length(Vars) == Arity
%%%
function({_Module, _Function, _Arity} = MFA, F,
	 Forms, Options) when is_function(F) ->
    parse_transform(MFA, F, Forms, Options).

parse_transform(MFA, Fun, Forms, _Options) ->
    [File|_] = [F || {attribute,_,file,{F,_}} <- Forms],
    try begin
	    NewTree = xform(MFA, Fun, Forms, [{file, File}]),
	    revert_tree(NewTree)
	end
    catch
	throw:{error,Ln,What} ->
	    {error, [{File, [{Ln,?MODULE,What}]}], []}
    end.

revert_tree(Tree) ->
    [erl_syntax:revert(T) || T <- lists:flatten(Tree)].


format_error(Other) ->
    lists:flatten(
      io_lib:format("unknown error in parse_transform: ~p", [Other])).




xform({M,F,A}, Fun, Forms, Context0) ->
    Bef = fun(function, Form, Ctxt) ->
		  {Fname, Arity} = erl_syntax_lib:analyze_function(Form),
		  VarNames = erl_syntax_lib:new_variable_names(
			       Arity,
			       erl_syntax_lib:variables(Form)),
		  {Form, [{function, Fname},
			  {arity, Arity},
			  {var_names, VarNames}|Ctxt]};
	     (_, Form, Context) ->
		  {Form, Context}
	  end,
    Aft = fun(application, Form, Context) ->
		  case erl_syntax_lib:analyze_application(Form) of
		      {M, {F, A}} ->
			  add_ann(
			    bind_state,
			    Fun(Form, Context));
		      _ ->
			  Form
		  end;
	     (function, Form, Context) ->
		  Form1 =
		      erl_syntax_lib:map_subtrees(
			fun(Clause) ->
				case should_i_bind(Clause) of
				    true ->
					Pats = clause_patterns(Clause),
					CBod = clause_body(Clause),
					CGd = clause_guard(Clause),
					Pats1 =
					    lists:zipwith(
					      fun(V, P) ->
						      match_expr(v(V), P)
					      end,
					      proplists:get_value(
						var_names, Context),
					      Pats),
					clause(Pats1, CGd, CBod);
				    false ->
					Clause
				end
			end, Form),
		  Form1;
	     (_, Form, _Context) ->
		  Form
	  end,
    [Module] = [Mx || {attribute, _, module, Mx} <- Forms],
    transform(Forms, Bef, Aft, [{module, Module}|Context0]).


transform(Forms, Before, After, Context) ->
    F1 =
	fun(Form) ->
		Type = erl_syntax:type(Form),
		{Form1, Context1} =
		    try Before(Type, Form, Context)
		    catch
			error:Reason ->
			    ?ERROR(Reason, 'before', Before, 
				   [{type, Type},
				    {context, Context},
				    {form, Form}])
		    end,
		Form2 =
		    case erl_syntax:subtrees(Form1) of
			[] ->
			    Form1;
			List ->
			    NewList =
				transform(
				  List, Before, After, Context1),
			    erl_syntax:update_tree(Form, NewList)
		    end,
		Type2 = erl_syntax:type(Form2),
		try After(Type2, Form2, Context1)
		catch
		    error:Reason2 ->
			?ERROR(Reason2, 'after', After, 
			       [{type, Type2},
				{context, Context1},
				{form, Form2}])
		end
	end,
    F2 = fun(List) when is_list(List) ->
		 map(F1, List);
	    (Form) ->
		 F1(Form)
	 end,
    map(F2, Forms).

%%% Slightly modified version of lists:mapfoldl/3
%%% Here, F/2 is able to insert forms before and after the form
%%% in question. The inserted forms are not transformed afterwards.
map(F, [Hd|Tail]) ->
    {Before, Res, After} =
	case F(Hd) of
	    {Be, _, Af} = Result when is_list(Be), is_list(Af) ->
		Result;
	    R1 ->
		{[], R1, []}
	end,
    Rs = map(F, Tail),
    Before ++ [Res| After ++ Rs];
map(F, []) when is_function(F, 1) -> [].



rpt_error(Reason, BeforeOrAfter, Fun, Info) ->
    Fmt = lists:flatten(
	    ["*** ERROR in parse_transform function:~n"
	     "*** Reason     = ~p~n"
	     "*** applying ~w fun (~p)~n",
	     ["*** ~10w = ~p~n" || _ <- Info]]),
    Args = [Reason, BeforeOrAfter, Fun | 
	    lists:foldr(
	      fun({K,V}, Acc) ->
		      [K, V | Acc]
	      end, [], Info)],
    io:format(Fmt, Args).


should_i_bind(Tree) ->
    erl_syntax_lib:fold(
      fun(T, Flag) ->
	      lists:member(bind_state, get_ann(T)) or Flag
      end, false, Tree).



v(V) ->
    erl_syntax:variable(V).


More information about the erlang-questions mailing list