Tic-tac-toe in Erlang — macros for testing and debugging

This is part of an Erlang tutorial built around a tic-tac-toe program. The program is stuffed into one file, called tic.erl and available here. The source code and this tutorial are organized into these sections:

Macros for testing and debugging

Erlang has a macro preprocessor that’s modeled on the C/C++ preprocessor. The tic.erl source file starts with two utility macros, which are at the top because you have to define them before using them. m_assert(P) confirms that P is true while m_confirm(Fn,Expr) returns the value from an expression, but first confirms the value has the expected form.

Assert and confirm are an important part of programming. By stating and testing assumptions, they help catch bugs early and identify integration problems. They perform regression testing, pinning down behaviors and interfaces. They also are an important part of unit testing, and they highlight areas of responsibility with entry/exit testing.

Source code

The macros looks like this.

% --------------------------------------------------------------
% Utility macros
% --------------------------------------------------------------
% ?m_assert( Pred )
% ?m_confirm( Confirm_fn, Expr )
%
%   Simple assert and confirm macros.
%
%   m_assert alerts the user and halts the program if Pred is
%   any value other than 'true'. With debugging off Pred is not
%   evaluated.
%
%   m_confirm( Confirm_fn, Expr ) returns (Expr), but before
%   returning it asserts that Confirm_fn( Expr) evaluates
%   true.
%
%   If you want something a little more comprehensive try:
%     http://erlang.org/download/eunit.hrl

-ifdef( NODEBUG ).

- define( m_assert( Pred ), ok ).
- define( m_confirm( Confirm_fn, Expr ), (Expr) ).

-else.

- define(
m_assert( Pred ),
  ( (fun ( Pred_evaled ) ->
       case Pred_evaled of
         true -> ok;
         _ ->
           io:fwrite( "~nAssert failed in ~w at line ~w~n",
             [ ?MODULE, ?LINE ]),
           io:fwrite( "Expression is "),
           io:fwrite( ??Pred),
           io:fwrite( "~n  which evaluates to ~w~n~n",
             [ Pred_evaled ]),
           erlang:error( assertion_failed)
       end
     end
    )( Pred)
  )).

- define(
m_confirm( Confirm_fn, Expr ),
  ( (fun ( Expr_evaled ) ->
       Confirm_evaled = Confirm_fn( Expr_evaled),
       case Confirm_evaled of
         true -> ok;
         _ ->
           io:fwrite( "~nConfirm failed in ~w at line ~w~n",
             [ ?MODULE, ?LINE ]),
           io:fwrite( "Expression is "),
           io:fwrite( ??Expr),
           io:fwrite( "~n  which evaluates to ~w~n",
             [ Expr_evaled ]),
           io:fwrite( "~nConfirm function is "),
           io:fwrite( ??Confirm_fn),
           io:fwrite( "~n  which returned ~w~n~n",
             [ Confirm_evaled ]),
           erlang:error( confirm_failed)
       end,
       Expr_evaled
     end
    )( Expr)
  )).

-endif.

Code review

Yes, I know, it’s kinda ugly. That’s how macros are. And is m_assert really a better name than plain assert. I was thinking the ‘m_‘ prefix identifies it as a macro, although macros are already obvious in Erlang, since you prefix them with a question mark to use them. So maybe m_ isn’t such a great idea.

And of course I probably should just use the macros in http://erlang.org/download/eunit.hrl. This wheel is already invented.

These are the kinds of points that come up in a code review. Inefficient and badly designed code is identified, stylistic idiosyncrasies are brought to light, and redundant/unnecessary code is weeded out. Code reviews are a good thing. Especially over pizza.

Let Binding

I’d like to point out an interesting feature of these macros. They bind their arguments by evaluating them and passing them to a function. It’s very much like the let macro does in Scheme — evals and passes the results to a lambda. This is as easy in Erlang as it is in Lisp.

Try this in an Erlang listener:

1> (fun( X ) -> X + X + X end)
1> (begin io:fwrite( "eval'ed~n"), 1 + 2 + 3 end).
eval'ed
18
2>

The begin..end statement is evaluated only once, where the result is bound to X and passed to the unnamed function.

In a pure functional language without side effects, it doesn’t matter how many times you evaluate an expression (except in how it affects performance). An expression without side effects always returns the same value and leaves no traces. You can change the order of evaluation, short-circuit evaluation, eval once and cache the value — it doesn’t matter.

But Erlang isn’t free of side effects (except in guards). So the let-like cliche demonstrated above is useful.

Comments

Leave a Reply