Reading Module Attributes in Erlang

11:20 pm Erlang, Programming, Tools and Libraries

[digg-reddit-me]Oddly, the Erlang standard library does not have a function for reading out module attributes.  One is relatively straightforwardly hacked together with beam_lib, if you know what you’re doing, but most people wouldn’t know to look there, and it’s kind of a hassle to write.

So, I wrote it for you.

Usage is straightforward: scutil:get_module_attribute(lists, export).

get_module_attribute(Module,Attribute) ->

    case beam_lib:chunks(Module, [attributes]) of

        { ok, { _, [ {attributes,Attributes} ] } } ->
            case lists:keysearch(Attribute, 1, Attributes) of
                { value, {Attribute,[Value]} } -> Value;
                false                          -> { error, no_such_attribute }
            end;

        { error, beam_lib, { file_error, _, enoent} } ->
            { error, no_such_module }

    end.

12 Responses

  1. Luke Gorrie Says:

    MyMod:module_info(attributes) ?

  2. John Haugeland Says:

    No. If you want, for example, the author attribute of the testerl module, you would write:

    scutil:get_module_attribute(testerl, author).

  3. Chaneisha Says:

    This really should be in the standard library.

  4. John Haugeland Says:

    Chaneisha: I agree.

  5. Alain O'Dea Says:

    module_info(attributes)) returns a proplist. You can get the value of the author attribute with
    Authors = proplists:get_all_values(author, module:module_info(attributes))..

    The same applies to other attributes and can be used to retrieve compile details as well. With SourceFile =proplists:get_value(source, complete:module_info(compile))). you can retrieve the source file name for a module.

    You can have the same attribute in a module more than once (very good for identifying multiple authors). Based on my tests scutil:get_module_attribute/2 throws an exception in this case.

    Unfortunately beam_lib is also not savvy to the code path because it works directly on BEAM files. As a result if you are loading modules outside the current working directory scutil:get_module_attribute/2 will return {error, no_such_module}.

    Thank you for sharing scutil, it has some neat features (I particularly like type_of/1).

    Grassroots Open Source activity like this in the Erlang community is hugely important. Keep up the good work!

  6. John Haugeland Says:

    Mr O’Dea: Your commentary regarding get_module_attribute is well heeded, and I’ll consider what to do.

    As far as scutil, don’t consider it released until you look at the project tracker list; there are more than 200 functions involved. I’m moving them one at a time in order to get a chance to clean things up somewhat and to get people like you to tell me the things I didn’t know, so that I have some chance of keeping up with the changes as such required.

    Incidentally, you can find all the scutil resources at scutil.com. Also, there are several more libraries coming in the near future.

    In general, I appreciate the kind commentary, but in the somewhat inappropriately chosen words of ODB, “Nigga, I’s just gettin started.”

  7. Alain O'Dea Says:

    Here’s a drop-in replacement for scutil:get_module_attribute/2 that preserves the return values:

    get_module_attribute(Module, Attribute) ->
    try
    Attributes = Module:module_info(attributes),
    try
    case proplists:get_value(Attribute, Attributes) of
    undefined -> {error, no_such_attribute};
    Value -> Value
    end
    catch
    _:_ -> {error, no_such_attribute}
    end
    catch
    _:_ -> {error, no_such_module}
    end.
    .

    If you are willing to change the return values scutil:get_module_attribute/2 you can take let it crash approach:

    get_module_attribute(Module, Attribute) when is_atom(Module), is_atom(Attribute) ->
    proplists:get_value(Attribute, Module:module_info(attributes)).

    The let it crash approach leaves it up to the user to interpret thrown exceptions when they send in garbage like non-atoms for the parameters, or non-existent modules. Let it crash is standard practice for library development in Erlang and Java. In both languages it is considered a best practice for libraries to let exceptions through so that client code can decide how to recover.

  8. John Haugeland Says:

    I confess, this is one of the parts of the Erlang mindset that confounds me.

    In what way would removing the error reporting make handling the error easier to tolerate? Provided as stands now, if you want it to propogate up the ownership tree (or other ownership arrangement), just don’t put an {error,E} clause in your pattern match, and your crash will be generated.

    The germane issues that led me to chose to handle the errors in code are three:

    1) Handling the error upstream by way of the exception generated is cumbersome and requires the user to learn to read the beam_lib errors

    2) Having the error handled as a straightforward tuple makes both the library code and the dependant code substantially easier to read and to reason about

    3) This allows me to draw a clear line in the sand between expected errors and unexpected errors

    There’s a whole lot of kool-aid drinking going on in the Erlang community. In my path through the various languages I speak, I’ve found one of my most important behavioral traits is the combination of the willingness to ask what the underlying benefit is to a behavior, combined with my willingness to put in the time to find people who can explain, ask them, and really work over their responses.

    Indeed, the Erlang standard library itself seems pretty divided on the issue – note the response from primitives like whereis(not_a_pid).

    So, let me be the first. What value does allowing it to crash in the library provide which is not equivalent to a user declining to handle managed errors, and which is superior to those things mentioned above?

    Simply put, what specific good does it do to not handle them?

  9. Alain O'Dea Says:

    John, you are right. It is helpful to handle these errors in the library and provide some meaningful error to the user. How about using exit(no_such_attribute) instead of {error, no_such_attribute}?.

    Returning errors as values has tangly consequences:
    Author = scutil:get_module_attribute(lists, author).

    Here Author is bound to {error, no_such_attribute}. How does the client-code decide that Author is an error here? How does client-code decide in general that a return value from scutil:get_module_attribute/2 is an error?

  10. John Haugeland Says:

    Here I would argue that returning it as a value is in fact a significant boon to identifiability. Returning it as an exception is essentially context free – the exception could be bubbling up from any child process, and there’s no particularly useful way to label the exceptions to distinguish one from another.

    On the balance, I honestly feel that context in the direct case is fairly clear:

    TestSuite = scutil:get_module_attribute(YourModule, testsuite).

    If TestSuite’s contents are {error,no_such_module} or {error,no_such_attribute}, it’s in each case pretty clear what happened: in the first case, the user referred to a module that didn’t exist (probable typo), and in the second case, the module isn’t set up for that testing rig.

    Here Author is bound to {error, no_such_attribute}. How does the client-code decide that Author is an error here?

    Er. I worry that I’m misunderstanding the question. If you’re asking how a client safely uses the function, there are two approaches: to handle common errors it’s just

    case scutil:get_module_attribute(SomeModule, SomeAttribute) of
    {error,E} -> {error,E};
    ResultValue -> do_stuff_with(Answer)
    end.

    And if you prefer the let it crash mindset, it’s just

    Foo = scutil:get_module_attribute(Bar,Baz),
    do_something_requiring_an_atom(Foo).

    The germane issue here in my mind is that returning clean sensible errors makes it very obvious what’s going on, requires no special code upstream, allows the user to have the let it crash mindset or the handle obvious problems mindset, and presents a much clearer difference between normal things being dumb and oh my god something caught fire this never happens kind of errors.

    I understand that in the primary sense, the let it crash mindset coupled with code loading and crash tolerance lead to fault resistant systems from which defects are removed early due to kersplosions. That said, I just don’t believe this is such a case, any more than having whereid(not_a_pid) crashing would be.

    Maybe I’m wrong. If I am, please, help me see the active value of using a crash to communicate this. I understand how to do it. What I need to understand is why.

  11. Alain O'Dea Says:

    Wow. I really went off base on this. For distributed errors exceptions make sense, but not for library errors.

    What do you think of returning good results in the form {ok Result}? Functions like file:open/2 and io:read/1 both yield {ok, Result}|{error, Error}. This makes an pattern matching exception occur at the point of invocation. This avoids the need for a explicit check of the results using a case statement.

    Scrap my nonsense about exceptions and let it crash. It doesn’t apply here :)

  12. John Haugeland Says:

    What do you think of returning good results in the form {ok Result}? … This avoids the need for a explicit check of the results using a case statement.

    That’s a good idea. That also removes ambiguity in the tremendously unlikely case that the actual contents of an attribute are {error,E}.

    Scrap my nonsense

    Not at all. I learned several things to keep in consideration for other parts of my libraries for this conversation, and I’m quite glad you helped me as such. Thank you.

Leave a Comment

Your comment

You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Please note: Comment moderation is enabled and may delay your comment. There is no need to resubmit your comment.