plain_fsm - for beginners and purists

Shawn Pearce spearce@REDACTED
Thu Feb 12 01:40:49 CET 2004


Ulf Wiger <ulf.wiger@REDACTED> wrote:
> >I'd suggest a different parse transform, or just a plain macro:
> >
> >	idle(S) ->
> >		receive
> >		'SYSTEM' -> 'SYSTEM';
> >		do_nothing -> idle(S)
> >		end.
> 
> This construct suffers from the same problem as my wrapper
> function. The code above would terminate if it received the
> message 'SYSTEM' (unless you know that there is a transformation,
> of course.)

Doh!  Clearly you are correct.

> I've been down this path a couple of times, but a problem that's
> difficult to get around is that the ?PLAIN_FSM_SYSTEM macro needs
> to pass the state (the variable S in the above example), and there
> is just no elegant way to do that, I think.

What about:

idle(S) ->
	receive
	'SYSTEM' -> idle(S);
	do_nothing -> idle(S)
	end.

Where the parse transform becomes:

-export([idle/1]).

idle(S) ->
	receive
	{system, From, Msg} ->
		handle_system_msg(...., {?MODULE, idle, S});
	{'EXIT', Parent, Reason} ->
		parent_EXIT(...);
	do_nothing -> idle(S)
	end.

This way the caller can pass you the state.  The convention being
the caller must perform a tail-recursive call to the next state it
should enter after a system message, and must pass the state as its
sole argument.  The parse transform ensures the state is exported,
allowing code changes to occur across system messages (rather than
using an fun).

I'm not up on writing parse transforms, so I don't know how ugly it
would be to force exporting the function.  But this is perhaps the
most natural way to write it...  I could see it being a compile error
if the user did something like:

	recieve 
	'SYSTEM' -> do_foo(), idle(S);
	...
	end

In other words, the block executed when 'SYSTEM' is received should
be a single expr, and only a local function call...

>    idle(S) ->
>       extended_receive
>          ...
>       end.
> 
> (One could also imagine a directive to replace the parser...,
> and why not the linter too, while we're at it. ;)

What is this, Perl?  :-)  I think this sort of muddies the language
some, especially what if two different types of extended_receive were
to be used at once?  What if I need to be a plain_fsm and want to also
have automatic handling of data received from gen_tcp by an active
socket?  With parse transforms like I'm suggesting above, this is
easily possible, both plain_fsm and gen_tcp could inline their clauses
right into the same receive statement.  But a single extended_receive
would be difficult otherwise.

> You change the spawn function of your FSM to one of :
>  - plain_fsm:spawn(Mod, Fun)
>  - plain_fsm:spawn_link(Mod, Fun)
>  - plain_fsm:spawn_opt(Mod, Fun)
> 
> These will perform some local magic and then call the corresponding
> functions in proc_lib.erl. Thus, you will get crash reports if the
> process dies.

Ah, silly me.  I didn't study proc_lib enough.  I just found that in
the init_p method of proc_lib; proc_lib will catch the execution of
the process, and build its own crash report.  I never noticed that it
would do this before, because typically gen_server and gen_fsm trap
the process faults themselves and write their own error reports, then
use exit(Reason) (where Reason is not {'EXIT',_}) to prevent proc_lib
from also writing a crash report.

So much to learn.  :-)

-- 
Shawn.



More information about the erlang-questions mailing list