Monthly Archives: January 2008

Dynamic variables hack in Erlang

One thing that jumps to the eye in functional code sources is longer-than-usual argument lists.


process(Tree, Env, Plugins, Config, ...).

It’s easy to see that as a consequence of functions using arguments as sole input channel. (Global -define()‘s don’t qualify as input channels—they can’t vary at runtime, arguments can.)

Usually it’s no big hassle.

Sometimes though you have a front-end function to a complex computation which is arranged in many layers of function calls, and you need to pass something to the front-end that only the lowest layer needs. You’d rather to not care about it in intermediate layers, but you still have to because intermediate layers forward it to the lowest one.

Obligatory contrived example (if you can read Ruby, Dave Thomas has a better one):


%% User invocation
library:do_it(A, B, C, D).

%% Library
do_it(A, B, C, D) ->
    part1(A) ++ part2(B, C, D).

part2(B, C, D) ->
    extra_computation(B) ++ part3(C, D).

part3(C, D) ->
    another_extra_computation(C) ++ part4(D).

%% ...

Above, D isn’t really needed until part4, but part1, part2 and part3 have to accept it as argument.

Functions being unable to see beyond their argument list spares me many a headache compared to when I dwell in OO-land, so I tend to deem it a feature, not a bug. In cases like the above however I’d like to relax boundaries. Especially when doing exploratory programming and part1, part2, partN have several clauses and invocation points, maintaining “forwarding” argument lists can impair your flow.

Common Lisp has dynamic variables. They’re best described through an example (leaving out the parentheses, since they scare away large chunks of the population):


> defvar *player* "Dr. Falken"
*PLAYER*

> defun player-greeting
>   concatenate 'string "Hello, " *player*
PLAYER-GREETING

> player-greeting
"hello, Dr. Flaken"

> let ((*player* "Matt")) player-greeting
"hello, Matt"

> player-greeting
"hello, Dr. Flaken"

let temporarily changes the binding of *player* for the scope player-greeting is evaluated in.

Here’s a way to do it in Erlang. (Read it up to the end, there are caveats.)

First, user’s point of view:


player_greeting() ->
    "Hello, " ++ dynvar:fetch(player).

test() ->
    Greeting = dynvar:with([{player, "Dr. Falken"}],
                           fun player_greeting/0),
    Greeting.

Invoking test/0 will produce "Hello, Dr. Falken".

The code:


-module(dynvar).
-export([with/2, fetch/1, test/0]).

with(Bindings, Action) ->
    lists:foreach(fun({Name, Value}) -> put(Name, Value) end, Bindings),
    try apply(Action, []) of
        X -> X
    after
        lists:foreach(fun({Name, _Value}) -> erase(Name) end, Bindings)
    end.

fetch(VarName) ->
    get(VarName).

Yes, the process dictionary seems to be at the epicenter of dirty Erlang hacks. :-)

A few considerations:

  • Common Lisp’s player-greeting can work without a “let” explicitly binding a value to *player*. The Erlang example can’t. This isn’t necessarily a drawback (see next point).
  • Syntax of the Erlang example is more verbose. A few bytes could be shaved off using macros and ditching dynvar:fetch/1, but that would only be a win in the eyes of the “beauty above all” crowd (surface beauty, let me add). First, indirecting via a macro would make life harder for whomever maintains the code six months down the road. Second, when you’re going against the grain of the language, you may want the comments to mention it, and you really want the code to scream it. (Do as I say, don’t do as I do.)
  • This may collide with other uses of the process dictionary. Consider adding a unique prefix to keys (e.g. dynvar-) before put()’ting them into the dictionary if that is an issue.
  • Where will you want to use this? Well, I’m using it in the internals of the seethrough template engine to avoid passing arguments through intermediate layers. Using it in library internals, preferably within a single module, not leaking signs of dynvar to the library user, is where I can advise to use this and still retain a clear conscience.