[erlang-questions] eep: multiple patterns

Richard A. O'Keefe <>
Mon May 26 04:54:17 CEST 2008


On 23 May 2008, at 11:05 pm, Mats Cronqvist wrote:

Hooray!  Evidence!
>  let me assure you that there are major telecom equipment vendors  
> whose
> code bases would benefit greatly from this syntax.
>  although i (no longer) has access to that code, the pattern appears
> quite often in event-driven programming. here's a very small example
> from a simple GUI application;
>
> loop(St) ->
>    receive
>    %% user deleted top window
>    {?MODULE,{signal,{window1,_}}}       -> quit();
>    %% user selected quit menu item
>    {?MODULE,{signal,{quit1,_}}}       -> quit();
>    %% user selected  connect menu item
>    {?MODULE,{signal,{connect,_}}}       -> loop(conn(St));
>    %% user selected  disconnect menu item
>    {?MODULE,{signal,{disconnect,_}}} -> loop(disc(St));
>    %% user selected about menu item
>    {?MODULE,{signal,{about1,_}}}       -> loop(show_about(St));
>    %% user clicked ok in about dialog
>    {?MODULE,{signal,{dialogb,_}}}      -> loop(hide_about(St));
>    %% user deleted about dialog
>    {?MODULE,{signal,{dialog1,_}}}       -> loop(hide_about(St));
>    %% we got data from the top_top process
>    {data,Data}                       -> loop(update(St,Data));
>    %% quit from the erlang shell
>    quit                              -> quit();
>    %% log other signals
>    X                                 -> io:fwrite("got ~p~n", 
> [X]),loop(St)
>    end.
>

Let me start by making two changes to that code.
The first is to indent it, so that I can read it.
The second is to group together things with the same body.

     loop(St) ->
         receive
	    quit ->
		%% quit from the Erlang shell
		quit()
	  ; {?MODULE,{signal,{window1,_}}} ->
		%% User deleted top window.
		quit()
	  ; {?MODULE,{signal,{quit1,_}}} ->
		%% User selected 'Quit' menu item.
		quit()
	  ; {?MODULE,{signal,{connect,_}}} ->
		%% User selected 'Connect' menu item.
		loop(conn(St))
	  ; {?MODULE,{signal,{disconnect,_}}} ->
		%% User selected 'Disconnect' menu item.
		loop(disc(St))
	  ; {?MODULE,{signal,{about1,_}}} ->
		%% User selected 'About' menu item.
		loop(show_about(St))
	  ; {?MODULE,{signal,{dialogb,_}}} ->
		%% User clicked 'OK' in "about" dialogue.
		loop(hide_about(St))
	  ; {?MODULE,{signal,{dialog1,_}}} ->
		%% User deleted "about" dialogue.
	  ; {data,Data} ->
		%% We got data from the top_top process.
		loop(update(St, Data))
	  ; X ->
		%% Log other signals.
		io:fwrite("got ~p~n", [X]),
		loop(St)
	end.

In this case, it matters that *every* message is to be received and
acted on.  That means that we can once again use the factorisation
technique.

     loop(State) ->
	receive
	    Message ->
		loop(State, classify(Message))
	end.

     classify(quit)				-> quit;
     classify({?MODULE,{signal,{window1,_}}})	-> quit;
     classify({?MODULE,{signal,{quit1,_}}})	-> quit;
     classify({?MODULE,{signal,{connect,_}}})	-> connect;
     classify({?MODULE,{signal,{disconnect,_}}})	-> disconnect;
     classify({?MODULE,{signal,{about1,_}}})	-> show_about;
     classify({?MODULE,{signal,{dialog1,_}}})	-> hide_about;
     classify({?MODULE,{signal,{dialogb,_}}})	-> hide_about;
     classify({data,Data})			-> {data,Data};
     classify(X)					-> {error,X}.

     loop(_, quit) ->
	quit();
     loop(State, connect) ->
	loop(conn(State));
     loop(State, disconnect) ->
	loop(disc(State));
     loop(State, show_about) ->
	loop(show_about(State));
     loop(State, hide_about) ->
	loop(hide_about(State));
     loop(State, {data,Data}) ->
	loop(update(State, Data));
     loop(State, {error,X}) ->
	io:fwrite("got ~p~n", [X]),
	loop(State).

I like this because it makes a clean separation between
"what are the actions" and "what are the names for the actions".

Of course you can in-line this.  Just as we can have
case/case, we can have case/receive, getting

     loop(State) ->
	case receive
		 quit                              -> quit
	       ; {?MODULE,{signal,{window1,_}}}    -> quit
	       ; {?MODULE,{signal,{quit1,_}}}      -> quit
	       ; {?MODULE,{signal,{connect,_}}}    -> connect
	       ; {?MODULE,{signal,{disconnect,_}}} -> disconnect
	       ; {?MODULE,{signal,{about1,_}}}     -> show_about
	       ; {?MODULE,{signal,{dialog1,_}}}    -> hide_about
	       ; {?MODULE,{signal,{dialogb,_}}}    -> hide_about
	       ; {data,Data}                       -> {data,Data}
	       ; X                                 -> {error,X}
	     end
	  of quit       -> quit()
	   ; connect    -> loop(conn(State))
	   ; disconnect -> loop(disc(State))
	   ; show_about -> loop(show_about(State))
	   ; hide_about -> loop(hide_about(State))
	   ; {data,D}   -> loop(update(State, D))
	   ; {error,X}  -> io:fwrite("got ~p~n", [X]),
			   loop(State)
	end.

Now my first reaction on seeing a whole raft of {?MODULE,{signal, 
{Foo,_}}}
patterns was "this is too hard for me to read".  I would be trying to
simplify the protocol.  Separating the receive protocol from the action
selection makes this kind of maintenance easier.  Inlining means that
the versions with separate functions and with nested case/receive are
operationally equivalent.  A few hacks in the compiler could produce
essentially the same code as the original version, although I doubt that
it would be worth bothering.

Tastes vary.  To me, what we can write now in Erlang as it stands,
separating "what are the actions" from "what are the names for the  
actions",
is easier to read and understand than anything using multiple patterns
would be, not because multiple patterns are a bad idea as such, but  
because
combining multiple *complex* patterns into a single match makes the code
harder to read.  Multiple *simple* patterns might make for a more
interesting example.

--
"I don't want to discuss evidence." -- Richard Dawkins, in an
interview with Rupert Sheldrake.  (Fortean times 232, p55.)









More information about the erlang-questions mailing list