[b] 2014 Sep 18
[m] 2015 Mar 18

Labeled and Optional Arguments in Erlang

One of the things I found myself wishing for in Erlang was labeled arguments. Even with common operations from stdlib (like lists:foldl) - I constantly forget the correct order of arguments, and it becomes that much more error-prone in complex cases (see cowboy_req:new/14, for instance).

Another desirable feature is optional arguments, which are assigned a default value when omitted.

It turns-out that the semantics of these requirements are fully met by records!

I only realized this when lamenting the lack of the same features in SML (as compared to OCaml, which does have them) and being pointed to an alternative - anonymous records.

However, proliferation of public record definitions is a bit problematic in Erlang, due to lack of features for managing name spaces, but even that can be resolved by following the same technique that is already used to solve this problem in modules: fully qualify any public name, i.e. foo_bar_baz, not just baz. To tidy things up even more, I think it is nice to keep public record definitions in dedicated .hrl files, i.e. foo_bar.hrl, foo_baz.hrl, not just records.hrl.

module.erl:

-module(module).

-include("module_args.hrl").

-export_type(
    [ args_foo/0
    ]).

-export(
    [ foo/1
    ]).

-type args_foo() ::
    #module_args_foo{}.

-spec foo(args_foo()) ->
    ok.
foo(#module_args_foo
    { bar = Bar
    , baz = Baz
    , qux = Quxicles
    }
) ->
    ok = do_stuff_with_quxicles(Quxicles, Bar, Baz).

module_args.hrl:

-record(module_args_foo,
    { bar = <<"default">> :: binary()
    , baz                 :: integer()
    , qux = []            :: [stuff()]
    }).

This may be too heavy of a sledge hammer for most cases, but when your function really can't help but need many arguments - this technique can aid understanding at the cost of some inconvenience.

I used this technique extensively in my OAuth 1 server protocol implementation.