Peter Miller

Musings on Technology and Programming
in

Pen & Paper Programming (Part 3) - Annotated Programs

I recently posted about how I used code simulation to understand a piece of Erlang code by explicitly writing out the expansion of a function with a given input. That was truly a manual effort and while it was useful to me, there are of course ways of following the flow of a program without all that writing.

The technique I'll focus on in this post is using console output statements. Like writing out a function expansion by hand, using this technique is not going to convince anyone of your coding prowess. However, if like me, you often learn best by seeing a program work, then this approach can be a great learning tool. If you use them wisely, your output statements become annotations, like a mini set of CliffNotes for your program.

So far this is a pretty vanilla point that I'm making. So, let's narrow the focus a little bit with a concrete example, another Erlang program, also from Programming Erlang. The point of this program is to create a ring of linked processes and send a message around the ring various numbers of times. By timing the execution and varying the number of processes in the ring and the number of times the message goes around you presumably gain some insight into Erlang's proficiency at handling a large number of processes at once.

The author, Joe Armstrong, does not provide a solution in the book. After some hacking around, I got pretty close, then looked online and found a solution that I could understand, kindly provided on drc's blog, Halfbaked Ideas. It is actually pretty well commented code, but even with the comments, I was not totally comfortable with it. So I created my own "annotated" version by adding a few more output statements:

-module(ring).
-export([start/2, foo/1, bar/1, timer/0]).

%%----------------------------------------------------
%% Create N processes in a ring.
%% Send a message round the ring M times,
%% so that N * M messages get sent.
%% Time performance for different values of N and M.
%%----------------------------------------------------

foo(Next) ->
receive
{0, Any} ->
io:format("foo process ~p sending stop message to process ~p~n",[self(),Next]),
Next ! {0, Any}, %% send the 0 to tell everyone to shut down
io:format("foo process ~p stopped itself~n",[self()]);
{N, Any} ->
io:format("foo process ~p sending message to process ~p~n",[self(),Next]),
Next ! {N, Any},
io:format("foo process ~p back in receive loop~n",[self()]),
foo(Next)
end.

bar(Timer) ->
receive
{0, Next} ->
io:format("bar process ~p stopping timer~n",[self()]),
Timer ! done,
io:format("bar process ~p stopped itself~n",[self()]);
{N, Next} ->
io:format("~nbar process ~p starting a round of messages by sending to process ~p~n",[self(),Next]),
Next ! {N-1, Next},
bar(Timer)
end.

timer() ->
statistics(runtime),
statistics(wall_clock),
receive
done ->
{_, RT} = statistics(runtime),
{_, WC} = statistics(wall_clock),
io:format("Total time=~p (~p) milliseconds~n", [RT, WC])
end.

%%----------------------------------------------------
%% construct a chain of processes
%% process J-1 routes msgs to process J for all J < K
%%----------------------------------------------------
chain(0, Pid) -> Pid;
chain(K, Pid) ->
chain(K-1, spawn(ring,foo,[Pid])).

%%—————————————————-
%% N is the number of processes in the ring
%% M is the number of times msgs circle the ring
%%—————————————————-
start(N, M) ->
%% N-1 processes are of type foo, one is of type bar
A= spawn(ring, bar, [spawn(ring, timer,[])]),
B= chain(N-1, A),
A ! {M, B}.

As I said in my previous posts, I can't hope to teach everyone Erlang and I assume most visitors here are unfamiliar with it. So this code still probably doesn't make sense to most of you. With that in mind, take a look at the output from running this program with a ring of 5 processes and the message going around twice:

>ring:start(5,2).
{2,<0.10079.0>}
bar process <0.10075.0> starting a round of messages by sending to process <0.10
079.0>

foo process <0.10079.0> sending message to process <0.10078.0>
foo process <0.10079.0> back in receive loop
foo process <0.10078.0> sending message to process <0.10077.0>
foo process <0.10078.0> back in receive loop
foo process <0.10077.0> sending message to process <0.10076.0>
foo process <0.10077.0> back in receive loop
foo process <0.10076.0> sending message to process <0.10075.0>
foo process <0.10076.0> back in receive loop

bar process <0.10075.0> starting a round of messages by sending to process <0.10
079.0>
foo process <0.10079.0> sending stop message to process <0.10078.0>
foo process <0.10079.0> stopped itself
foo process <0.10078.0> sending stop message to process <0.10077.0>
foo process <0.10078.0> stopped itself
foo process <0.10077.0> sending stop message to process <0.10076.0>
foo process <0.10077.0> stopped itself
foo process <0.10076.0> sending stop message to process <0.10075.0>
foo process <0.10076.0> stopped itself
bar process <0.10075.0> stopping timer
bar process <0.10075.0> stopped itself
Total time=16 (16) milliseconds

This may seem like an almost trivially basic technique, but that criticism is only half right. It is a basic technique, but it is not trivial to examine a program this way. Just think of it as a whiteboard session without the smell.

Posted: Nov 05 2007, 10:20 PM by pmiller | with no comments
Filed under:

Comments

No Comments

Leave a Comment

(required) 

(required) 

(optional)

(required)