plain_fsm - for beginners and purists

Ulf Wiger ulf.wiger@REDACTED
Thu Feb 12 07:45:52 CET 2004


On Wed, 11 Feb 2004 19:40:49 -0500, Shawn Pearce <spearce@REDACTED> 
wrote:

> idle(S) ->
> 	receive
> 	'SYSTEM' -> idle(S);
> 	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).

One interesting twist of the above is that it will actually
_appear_ to work, even if you forget to do the parse_transform -
it just doesn't handle system messages like you thought it
would. The wrapper function used in plain_fsm will actually
exit if you ever call it directly. So if you forget to do
the parse_transform, your program will exit immediately
with a reasonably descriptive exit reason.

Actually, very few code changes break funs nowadays.
I thought using a fun was the (conceptually) cleanest way
of passing the continuation, but plain_fsm also supports
passing the name of the function (which then has to be
exported.)

You can force functions to be exported in a parse_transform.
It doesn't make the code that much uglier than it already is.
But I dislike handing the programmer surprises like automatically
exporting a user-provided function. I think the programmer
should do it, if it needs to be done.


> 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...

Actually, if you take the expressions given in that particular
receive clause and wrap them inside a fun, you have your
continuation. Then, it doesn't matter if there's more than
one expression. There's still the question of which construct
is the least bewildering to the user... (:


>
>>    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.

I don't see the difference, really. You're still doing a parse_transform,
and even with the (receive 'SYSTEM' -> ... end) construct, you have to
trust that the parse_transform does what you think it does. The only
real difference is how you signal the customized semantics. I think
it's a lot clearer to use a unique keyword which cannot be mistaken
for valid Erlang with already given semantics.

The only thing to worry about in the case of using a keyword is
that it eventually become part of the Erlang language but then
meaning something else. Solution - use a keyword that couldn't
possibly become standard:

    idle(S) ->
       plain_fsm_receive
          ...
       end.

or, since we're transforming tokens anyway, why not invent something
entirely different -- a user-provided keyword syntax:

    idle(S) ->
       |plain_fsm::receive|
            ...
       end.

This would even make it easier to transform the token stream,
since we're not using an atom (which could occur anywhere),
but using a sequence of tokens that at least I have never come
across in an Erlang module before.

The way I'd do it is to replace the token sequence with tokens
representing valid erlang code, and then triggering a parse
transform, almost exactly like in plain_fsm today.

(It would also mess up Emacs indentation, of course... bummer.)

/Uffe
-- 
Ulf Wiger




More information about the erlang-questions mailing list