Problem
Often event handlers share some logic in common. Currently, there's no great way to encapsulate this shared logic in Frame.
The best current solution is to define an additional event in the interface block, implement a handler for that event that contains the shared logic, then directly invoke this handler from the handlers with the shared functionality.
For example:
-interface-
foo
bar
shared
-machine-
|foo|
... stuff specific to foo ...
shared ()
|bar|
... stuff specific to bar ...
shared ()
|shared|
... shared logic, e.g. decide whether to transition ...
However, there are some drawbacks of this approach. The obvious drawback is that shared shows up in the public interface of the machine, when really it's a subroutine that shouldn't be invoked from the outside.
A much less obvious drawback is that since shared is just a normal event, it's handler may or may not transition, and so we have to enforce some limitations around this kind of usage to ensure that we don't end up executing handler code after a transition (which can lead to bad situations, such as a double transition).
Currently, the Rust backend treats this case conservatively, treating direct handler invocations (like the use of shared () above) essentially as terminators. So you cannot, for example, write:
|baz|
... stuff specific to baz ...
shared ()
... more stuff specific to baz (this code can never be executed in the Rust backend) ...
We could loosen this restriction by tracking whether or not we've transitioned at runtime, but then that leads to potentially confusing behavior where code after a handler will sometimes be executed and sometimes not.
Solution? Internal/private events
I'm not sure what the best solution is here, but one idea is to provide a way to define "internal" or "private" events. This would solve the issue of polluting the public interface, and Mark has said that this would also be useful for realizing the actor model in Frame.
However, there is still the second problem described above, of tracking whether or not a handler has transitioned. I think there are four ways to resolve this:
- Disallow internal/private events from transitioning. I think this probably rules out too many useful cases.
- Conservatively require invocations of internal/private events to occur in terminator position, similar to the current solution in the Rust backend. I think this probably also rules out too many useful cases (e.g. having an internal event that just updates state variables but never transitions).
- Track at runtime whether a handler has transitioned, and stop execution of the calling handler if the callee transitioned. This is a bit more work and could lead to some confusing behavior, but admits the most cases.
- Separate internal/private events into those that may transition (i.e. they have a transition statement somewhere in them) and those that don't (no transition statements). Potentially transitioning events may be triggered only in terminator position, while non-transitioning events may be triggered anywhere. This would be a simple static approach that probably gives us most of the useful cases.
My vote is for resolution 3 or 4, leaning toward 4. But maybe there's some other solution altogether?
Problem
Often event handlers share some logic in common. Currently, there's no great way to encapsulate this shared logic in Frame.
The best current solution is to define an additional event in the interface block, implement a handler for that event that contains the shared logic, then directly invoke this handler from the handlers with the shared functionality.
For example:
However, there are some drawbacks of this approach. The obvious drawback is that
sharedshows up in the public interface of the machine, when really it's a subroutine that shouldn't be invoked from the outside.A much less obvious drawback is that since
sharedis just a normal event, it's handler may or may not transition, and so we have to enforce some limitations around this kind of usage to ensure that we don't end up executing handler code after a transition (which can lead to bad situations, such as a double transition).Currently, the Rust backend treats this case conservatively, treating direct handler invocations (like the use of
shared ()above) essentially as terminators. So you cannot, for example, write:We could loosen this restriction by tracking whether or not we've transitioned at runtime, but then that leads to potentially confusing behavior where code after a handler will sometimes be executed and sometimes not.
Solution? Internal/private events
I'm not sure what the best solution is here, but one idea is to provide a way to define "internal" or "private" events. This would solve the issue of polluting the public interface, and Mark has said that this would also be useful for realizing the actor model in Frame.
However, there is still the second problem described above, of tracking whether or not a handler has transitioned. I think there are four ways to resolve this:
My vote is for resolution 3 or 4, leaning toward 4. But maybe there's some other solution altogether?