plain_fsm - for beginners and purists
Thu Feb 12 07:45:52 CET 2004
On Wed, 11 Feb 2004 19:40:49 -0500, Shawn Pearce <spearce@REDACTED>
> idle(S) ->
> 'SYSTEM' -> idle(S);
> do_nothing -> idle(S)
> 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
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:
> 'SYSTEM' -> do_foo(), idle(S);
> 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) ->
>> (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:
or, since we're transforming tokens anyway, why not invent something
entirely different -- a user-provided keyword syntax:
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.)
More information about the erlang-questions