[erlang-questions] Defining static functions in abstract modules

Matthew Dempsky matthew@REDACTED
Wed Apr 9 11:04:54 CEST 2008


It would be nice to be able to define arbitrary static functions
within an abstract module.  Right now you can kludge around this by
exploiting that local functions named 'new' are always static and
write something like

    new(incr, X) -> X + 1.

but it would be nicer to just write

    -static([incr/1]).
    incr(X) -> X + 1.

Below is a very rudimentary patch that adds support for this syntax.
It's basically only enough to compile and run very simple test cases
and to demonstrate that implementing this does not seem too difficult.
(One problem I'm already aware of is that a static function foo/1 and
an instance function foo/0 silently collide without any warning... so
don't do that. ;-))

Another option would be to make the attribute statement just
"-static([incr])." and then all incr functions are defined static, but
that seems unnecessarily limiting.

If the long-term goal is to have separate namespaces for static and
instance functions (e.g., so (Mod:new(...)):module_info() works and
returns something meaningful for the module instance), then maybe a
syntax that actually decorates individual function definitions would
be better?

Are there any plans on adding static functions in a future release?


--- stdlib/src/erl_parse.yrl.orig	2007-11-27 07:57:32.000000000 -0800
+++ stdlib/src/erl_parse.yrl	2008-04-09 01:25:31.000000000 -0700
@@ -627,6 +627,12 @@
 	    {attribute,La,export,farity_list(ExpList)};
 	_Other -> error_bad_decl(La, export)
     end;
+build_attribute({atom,La,static}, Val) ->
+    case Val of
+        [ExpList] ->
+            {attribute,La,static,farity_list(ExpList)};
+        _Other -> error_bad_decl(La, static)
+    end;
 build_attribute({atom,La,import}, Val) ->
     case Val of
 	[Name] ->
--- compiler/src/sys_pre_expand.erl.orig	2008-02-05 05:37:09.000000000 -0800
+++ compiler/src/sys_pre_expand.erl	2008-04-09 01:26:06.000000000 -0700
@@ -38,6 +38,7 @@
                  parameters=undefined,          %Module parameters
                  package="",                    %Module package
                  exports=[],                    %Exports
+                 statics=[],                    %Statics
                  imports=[],                    %Imports
                  mod_imports,                   %Module Imports
                  compile=[],                    %Compile flags
@@ -109,6 +110,7 @@
 		 end,
             {Fs1,Xs,Ds} = sys_expand_pmod:forms(Fs0, Ps,
                                                 St0#expand.exports,
+                                                St0#expand.statics,
                                                 St0#expand.defined),
 	    St1 = St0#expand{exports=Xs, defined=Ds},
 	    {Fs2,St2} = add_instance(Ps, Fs1, St1),
@@ -220,6 +222,8 @@
               package = packages:strip_last(M)};
 attribute(export, Es, St) ->
     St#expand{exports=union(from_list(Es), St#expand.exports)};
+attribute(static, Ss, St) ->
+    St#expand{statics=union(from_list(Ss), St#expand.statics)};
 attribute(import, Is, St) ->
     import(Is, St);
 attribute(compile, C, St) when is_list(C) ->
--- compiler/src/sys_expand_pmod.erl.orig	2008-02-05 05:38:12.000000000 -0800
+++ compiler/src/sys_expand_pmod.erl	2008-04-09 01:32:36.000000000 -0700
@@ -25,18 +25,19 @@
 %% and 'exports'. The automatic 'new/N' function is neither added to the
 %% definitions nor to the 'exports'/'defines' lists yet.

--export([forms/4]).
+-export([forms/5]).

--record(pmod, {parameters, exports, defined, predef}).
+-record(pmod, {parameters, exports, statics, defined, predef}).

 %% TODO: more abstract handling of predefined/static functions.

-forms(Fs0, Ps, Es0, Ds0) ->
+forms(Fs0, Ps, Es0, Ss0, Ds0) ->
     PreDef = [{module_info,0},{module_info,1}],
-    forms(Fs0, Ps, Es0, Ds0, PreDef).
+    forms(Fs0, Ps, Es0, Ss0, Ds0, PreDef).

-forms(Fs0, Ps, Es0, Ds0, PreDef) ->
-    St0 = #pmod{parameters=Ps,exports=Es0,defined=Ds0, predef=PreDef},
+forms(Fs0, Ps, Es0, Ss0, Ds0, PreDef) ->
+    St0 = #pmod{parameters=Ps,exports=Es0,statics=Ss0,defined=Ds0,
+                predef=PreDef},
     {Fs1, St1} = forms(Fs0, St0),
     Es1 = update_function_names(Es0, St1),
     Ds1 = update_function_names(Ds0, St1),
@@ -50,15 +51,22 @@
     [update_function_name(E, St) || E <- Es].

 update_function_name(E={F,A}, St) when F =/= new ->
-    case ordsets:is_element(E, St#pmod.predef) of
+    case ordsets:is_element(E, St#pmod.predef) orelse
+        ordsets:is_element(E, St#pmod.statics) of
 	true -> E;
 	false -> {F, A + 1}
     end;
 update_function_name(E, _St) ->
     E.

-update_forms([{function,L,N,A,Cs}|Fs],St) when N =/= new ->
-    [{function,L,N,A+1,Cs}|update_forms(Fs,St)];
+update_forms([F0={function,L,N,A,Cs}|Fs],St) when N =/= new ->
+    F = case ordsets:is_element({N,A}, St#pmod.statics) of
+            true ->
+                F0;
+            false ->
+                {function,L,N,A+1,Cs}
+        end,
+    [F|update_forms(Fs,St)];
 update_forms([F|Fs],St) ->
     [F|update_forms(Fs,St)];
 update_forms([],_St) ->
@@ -74,9 +82,14 @@
     {[], St0}.

 %% Only function definitions are of interest here. State is not updated.
-form({function,Line,Name0,Arity0,Clauses0},St) when Name0 =/= new ->
-    {Name,Arity,Clauses} = function(Name0, Arity0, Clauses0, St),
-    {{function,Line,Name,Arity,Clauses},St};
+form(F = {function,Line,Name0,Arity0,Clauses0},St) when Name0 =/= new ->
+    case ordsets:is_element({Name0, Arity0}, St#pmod.statics) of
+        true ->
+            {F,St};
+        false ->
+            {Name,Arity,Clauses} = function(Name0, Arity0, Clauses0, St),
+            {{function,Line,Name,Arity,Clauses},St}
+    end;
 %% Pass anything else through
 form(F,St) -> {F,St}.

@@ -362,7 +375,13 @@
 expr({call,Lc,{atom,Lf,F},As0},St) ->
     %% Local function call - needs THIS parameter.
     As1 = expr_list(As0,St),
-    {call,Lc,{atom,Lf,F},As1 ++ [{var,0,'THIS'}]};
+    As2 = case ordsets:is_element({F, length(As1)}, St#pmod.statics) of
+              true ->
+                  As1;
+              false ->
+                  As1 ++ [{var,0,'THIS'}]
+          end,
+    {call,Lc,{atom,Lf,F},As2};
 expr({call,Line,F0,As0},St) ->
     %% Other function call
     F1 = expr(F0,St),



More information about the erlang-questions mailing list