Is it possible to find which clauses are defined for a function?

Is it possible to find which function clauses a function has defined?

For example if you have the following function exported in a module. Is it possible to find the 4 different clauses? Preferably without using epp.

example(0) -> nothing;
example(1) -> one;
example(2) -> two;
example(N) when N > 2 -> many.
1 Like

For a string you can do this:

1> String = "example(0) -> nothing;
   example(1) -> one;
   example(2) -> two;
   example(N) when N > 2 -> many.".
"example(0) -> nothing;\nexample(1) -> one;\nexample(2) -> two;\nexample(N) when N > 2 -> many."
2> {ok, T, _} = erl_scan:string(String), erl_parse:parse_form(T).
{ok,{function,1,example,1,
              [{clause,1,[{integer,1,0}],[],[{atom,1,nothing}]},
               {clause,2,[{integer,2,1}],[],[{atom,2,one}]},
               {clause,3,[{integer,3,2}],[],[{atom,3,two}]},
               {clause,4,
                       [{var,4,'N'}],
                       [[{op,4,'>',{var,4,'N'},{integer,4,2}}]],
                       [{atom,4,many}]}]}}

But that will not handle if you have macros or any other things that the epp handles.

If it is from code that can be compiled you can look at the AST from the debug_info chunk.

1 Like

Why? Do you want to do it on compiled modules or on the source code?

I’m not sure that is what you desire, but you can go with something like:

-module(foo).
-compile([debug_info]). %% Required!

-export([example/1]).

example(0) -> nothing;
example(1) -> one;
example(2) -> two;
example(N) when N > 2 -> many.

And then:

-module(ast_utils).

-export([fun_clauses/3]).

fun_clauses(Module, Fun, Arity) ->
    case mod_ast(Module) of
        {ok, AST} ->
            extract_fun_clauses(Fun, Arity, AST);
        error ->
            error
    end.

mod_ast(Module) ->
    case code:which(Module) of
        BeamFile when is_list(BeamFile) ->
            case beam_lib:chunks(BeamFile, [ abstract_code ]) of
                {ok, {_, [ {abstract_code, {_, AST}} ]} } ->
                    {ok, AST};
                _ ->
                    error
            end;

        _ ->
            error
    end.

extract_fun_clauses(Fun, Arity, AST) ->
    do_extract_fun_clauses(AST, Fun, Arity).

do_extract_fun_clauses([Form | Rest], Fun, Arity) ->
    case erl_syntax:type(Form) of
        function ->
            FFun = erl_syntax:atom_value(erl_syntax:function_name(Form)),
            FArity = erl_syntax:function_arity(Form),
            case {FFun, FArity} of
                {Fun, Arity} ->
                    {ok, erl_syntax:function_clauses(Form)};
                _ ->
                    do_extract_fun_clauses(Rest, Fun, Arity)
            end;
        _ ->
            do_extract_fun_clauses(Rest, Fun, Arity)
    end;
do_extract_fun_clauses([], _, _) ->
    error.

Result:

1> ast_utils:fun_clauses(foo, example, 1).
{ok,[{clause,{6,1},
             [{integer,{6,9},0}],
             [],
             [{atom,{6,15},nothing}]},
     {clause,{7,1},[{integer,{7,9},1}],[],[{atom,{7,15},one}]},
     {clause,{8,1},[{integer,{8,9},2}],[],[{atom,{8,15},two}]},
     {clause,{9,1},
             [{var,{9,9},'N'}],
             [[{op,{9,19},'>',{var,{9,17},'N'},{integer,{9,21},2}}]],
             [{atom,{9,26},many}]}]}
2 Likes

Preferably on compiled code. I want to create some zotonic specific introspection modules for html developers using the system.

We use constructs like found here in the email contact module.

Which html developers can access like this:

{% wire id="contact-form"
        type="submit"
        postback={contact email_template="email_contact.tpl"}
        delegate="mod_contact" %}
<form id="contact-form" method="post" action="postback">
 	<div class="form-group">
		<label for="name">{_ Name _}</label>
    	<input class="form-control" type
      ...

I want to make an introspection tool which makes it possible to easily see what postbacks there are in the system. We also use a similar construct we use to make model gets and calls available via templates, mqtt and http.

Like this: zotonic/apps/zotonic_mod_comment/src/models/m_comment.erl at master · zotonic/zotonic · GitHub

This can be used from a template like this {% for comment in m.comment.rsc[1234] %} which will return the desired comments, or by using mqtt… from javascript like this:cotonic.call("model/comment/rsc/1234").then(...) or the http api.

The thing is that the web developers typically can’t read erlang code, so it would be nice to have some introspection tool available.

Then you could go with what @williamthome mentioned, but I’d use erl_syntax module to parse your functions/clauses more easily. You could do it by hand, but using erl_syntax will result in more readable and safer code (because AST might change in the future).

2 Likes

You are right about erl_syntax, thanks @mmin!

I’ve updated my example:

3 Likes

Excellent… It looks like the extract function is also usable with the forms from epp. Good idea to use erl_syntax for this.

1 Like

Consider

f(1) -> foo;
f(2) -> bar.

g(X) -> case X
of 1 -> foo
; 2 -> bar
end.

h(X) when X =:= 1 -> foo;
h(X) when X =:= 2 -> bar;
h(X) when false =:= true -> ugh.

In the absence of exceptions, these are the same function. In fact many functional programming language compilers would start by turning f/1 into g/1. I have always felt that it was a mistake for the error response for f(3) and g(3) to be different, but too late now. I would expect a good compiler that was not handicapped by the distinction between clause mismatch and case mismatch to generate the same code for f/1, g/1, and h/1. So looking for a way to distinguish them seems seriously weird to me.

% man beam_lib

has an example of reconstructing the abstract source code from a BEAM file, so that might be useful to you. But it’s still seriously weird. What is the real-world problem you hope to solve by doing this?

2 Likes

“I want to make an introspection tool which makes it possible to easily see what postbacks there are in the system. We also use a similar construct we use to make model gets and calls available via templates, mqtt and http.”

There are two ways to do this. Run time and compile time. Run time is hard. It requires the source code to be available (either the actual source, or the abstract syntax in a BEAM file or elsewhere, or reconstructible).

Compile time is easy. Use annotations like -postback(Whatever). Write a little preprocessor to collect those and generate -export([postbacks/0]) and postbacks() → … (Yes, this is how Java does things. Java is not ALWAYS wrong. It is also how some Smalltalk systems hook new stuff into their Preferences UI.)

2 Likes

Thanks for this suggestion. It could indeed already work with some simple extra annotations which are easily queryable. We are a bit hesitant to add parse transforms or extra pre-processors to add magical exports though. Food for thought.

Too bad it is not possible to use record values in module attribute tags. That would make future extensibility easier for these kinds of things a little bit easier.

-record(info, {name, doc, extra}).
-postback(#info{name=create, doc="Create a new resource"}).

Interesting. Why are records not allowed in attributes?

My guess is that they are module attributes themselves.You get a bad attribute error when you use it like that.

1 Like

“We are a bit hesitant to add parse transforms or extra pre-processors to add magical exports though.”

Having got the camel of ‘magical exports’ down your throat, it seems a little odd to strain out the gnat of a preprocessor. A preprocessor seems to me like the least magical way to do it. There’ve been two “Programs as Data Objects” conferences that I know of, and numerous papers. Conferences and books un abundance on generative programming and program transformation. For me the key issue is maintainability. Annotations + a preprocessor are code that I can write and maintain AND if the derived code needs to be different, I can change the transformation once and the results get fixed everywhere. On the other hand, it means that I do not need to know anything about BEAM. To be honest, I cherish my ignorance of BEAM.

2 Likes