Reply To: Example: grouping messages for receive expressions

Home Forums Basic Erlang Example: grouping messages for receive expressions Reply To: Example: grouping messages for receive expressions

#37966

George F
Member

You are absolutely right, mpmiszczyk. Macro functions do require special treatment and should not be confused with regular functions, because macro functions do not create a scope.

In the example above, I actually didn’t follow my own discipline for writing macro functions because the worker’s loop was a simple receive-loop. In other words, there could be no overlap of variable names because Erlang’s pattern matching only binds variables for matches. Of course, the problem that you described (Chore = 1) can very easily happen in general, which means that macro functions should be defined very carefully.

I will show you how I deal with this. For example, suppose I wanted to write a for-loop repetition construct for Erlang — “repeat Procedure 5 times”. This is how I would do it with a macro:


-define(REPEAT(N,K,Self,Loop,Procedure),
	(fun() -> Loop = fun
		(0,_) -> ok;
		(K,Self) -> Procedure, Self(K-1,Self)
	end, Loop(N,Loop) end)()).

This macro creates an anonymous looping function and pastes Procedure into its body right before the tail recursion. Procedure is either an erlang expression or multiple expressions contained within a begin..end statement. Procedure can reference the iterator variable K if it wants to. Notice how I include the macro variables [K, Self, Loop] in the macro definition. This forces me to be explicit about which variables I would like to use for those roles. Example usage of this macro:


foo() -> % print the numbers 1 to 5
?REPEAT(5,IteratorVar,SelfReference,IterationLoop,
io:format("~p.~n",[6-IteratorVar])
).

The macro definition forces me to write out the names of the macro variables; that reminds me to check for previous assignment to those variables. If I was rigorous in my example above, I would define the Chore macro as:
-define(Chore(Loop,Status,Chore), ...).

Hopefully, that addresses your first argument — on to your second argument. Is there a way to produce the preprocessed source file and have your tracer refer to line-numbers of that file instead of your unprocessed source file? That way, you can have your cake and eat it too :)

Regarding your third argument. The receive clause-group macros are used to define the communication protocol between your processes. I think it is good practice to have named tuples for all your receive patterns; this makes it impossible mismatch clauses due to permissive variable bindings. I think you meant the following pattern in your argument:


{group1,0,1} -> one;
{group1,0,X} -> X;
{group1,A,X} when A == 3 -> {A,X};
{group1,_,X} -> X*X

This pattern should only happen within one clause group. Different clause groups are supposed to handle different TYPES of messages, which means that they should have different atoms in their patterns. For the remaining “ignore all others” clause _ -> ignore, you should always manually make sure to put it last and never include it in a clause group.

Regarding your fourth argument, I agree with you that communication clause-groups should be defined all together in a header file so that they can be reused. I didn’t do it in the example because I wanted to post all necessary code in one monolithic piece.