You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
State machines are everywhere in interactive systems, but they're rarely defined clearly and explicitly. Given some big blob of code including implicit state machines, which transitions are possible and under what conditions? What effects take place on what transitions?
There are [existing design patterns for state machines](https://en.wikipedia.org/wiki/State_pattern), but all the patterns I've seen complect side effects with the structure of the state machine itself. Instances of these patterns are difficult to test without mocking, and they end up with more dependencies. Worse, the classic patterns compose poorly: hierarchical state machines are typically not straightforward extensions.
There are [existing design patterns for state machines](https://en.wikipedia.org/wiki/State_pattern), but all the patterns I've seen complect side effects with the structure of the state machine itself. Instances of these patterns are difficult to test without mocking, and they end up with more dependencies. Worse, the classic patterns compose poorly: hierarchical state machines are typically not straightforward extensions. The functional programming world has [solutions](https://hackage.haskell.org/package/reactive-banana), but they don't transpose neatly enough to be broadly usable in mainstream languages.
Here I present a composable pattern for pure state machiness with effects. The pure-impure separation is inspired by _[functional core, imperative shell](https://www.destroyallsoftware.com/talks/boundaries)_; the modeling of novelty and effect by [Elm](http://elm-lang.org); the hierarchical composition by [Harel statecharts](http://www.inf.ed.ac.uk/teaching/courses/seoc/2005_2006/resources/statecharts.pdf).
Here I present a composable pattern for pure state machiness with effects, meant to be idiomatic in a typical imperative context. The pure-impure separation is inspired by _[functional core, imperative shell](https://www.destroyallsoftware.com/talks/boundaries)_; the modeling of novelty and effect by [Elm](http://elm-lang.org); the hierarchical composition by [Harel statecharts](http://www.inf.ed.ac.uk/teaching/courses/seoc/2005_2006/resources/statecharts.pdf).
The solution is implemented in Swift because I'm particularly interested in a solution which makes sense in a fairly typical imperative context, and because Swift's types help illustrate the structure I'm suggesting.
The solution is implemented in Swift because it's a typical imperative context, and because Swift's types help illustrate the structure I'm suggesting.
## The basic types
andymatuschak
revised
this gist Jul 28, 2016.
1 changed file
with
11 additions
and
11 deletions.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Here's an example based on [A Pattern Language of Statecharts](http://industriallogic.net/patterns/P22.pdf), figure 3:
![Drawing of a simple turnstyle state machine, implemented below](https://andymatuschak.org/states/figure3.png)
```swift
@@ -162,6 +163,7 @@ struct NoError {}
## Hierarchical state machines
Now, if you have a lot of states, you might want to start grouping them into "child" state machines, especially if there's a significant shared semantic. Here I rephrase the example state machine hierarchically: an inner state machine and an outer one, following figure 6 from "A Pattern Language of Statecharts."
![Drawing of the turnstyle state machine, depicted hierarchically](https://andymatuschak.org/states/figure6.png)
Critically, *no other types* must change: the code above can use HierarchicalTurnstyleState as a drop-in replacement. StateTypes are closed under hierarchical composition. This is a natural consequence of the data-oriented design, which takes advantage of the simpler fact that enums are closed over carrying instances of other enums as an associated value, and that functions are closed over calling other functions. Nothing more complicated is required.
andymatuschak
revised
this gist Jul 28, 2016.
1 changed file
with
4 additions
and
4 deletions.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Here's an example based on [A Pattern Language of Statecharts](http://industriallogic.net/patterns/P22.pdf), figure 3:
![figure 3](figure3.png)
![Drawing of a simple turnstyle state machine, implemented below](https://andymatuschak.org/states/figure3.png)
```swift
/// First up, the StateType. Note that this type is isolated, pure, trivial to fully test with zero mocks/stubs, etc.
@@ -162,7 +162,7 @@ struct NoError {}
## Hierarchical state machines
Now, if you have a lot of states, you might want to start grouping them into "child" state machines, especially if there's a significant shared semantic. Here I rephrase the example state machine hierarchically: an inner state machine and an outer one, following figure 6 from "A Pattern Language of Statecharts."
![figure 6](figure6.png)
![Drawing of the turnstyle state machine, depicted hierarchically](https://andymatuschak.org/states/figure6.png)
Critically, *no other types* must change: the code above can use HierarchicalTurnstyleState as a drop-in replacement. StateTypes are closed under hierarchical composition. This is a natural consequence of the data-oriented design, which takes advantage of the simpler fact that enums are closed over carrying instances of other enums as an associated value, and that functions are closed over calling other functions. Nothing more complicated is required.
@@ -271,7 +271,7 @@ The relationship between this kind of composition (orthogonal) and the earlier k
For instance, a keyboard might have two different state machines running simultaneously: one handles the toggling behavior of the main keypad's caps lock key and its impact on keypad behavior; the other handles the same thing for the numpad and num lock.
![keyboard state machine diagram](keyboard.png)
![Drawing of a keyboard's state machine, separating caps lock and num lock functionality](https://andymatuschak.org/states/keyboard.png)
```swift
structKeyboardState: StateType {
@@ -555,4 +555,4 @@ I'll save a detailed treatment on this topic for another day.
## Acknowledgements
My thanks to Colin Barrett, Justin Spahr-Summers, Rob Rix, and Chris Eidhoff for useful feedback on this draft.
My thanks to Colin Barrett, Chris Eidhof, Justin Spahr-Summers, and Rob Rix for useful feedback on this draft.
andymatuschak
renamed
this gist Jul 28, 2016.
1 changed file
with
0 additions
and
0 deletions.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# A composable pattern for pure state machines with effects
State machines are everywherein interactive systems, but they're rarely defined clearly and explicitly. Given some big blob of code including implicit state machines, which transitions are possible and under what conditions? What effects take place on what transitions?
There are [existing design patterns for state machines](https://en.wikipedia.org/wiki/State_pattern), but all the patterns I've seen complect side effects with the structure of the state machine itself. Instances of these patterns are difficult to test without mocking, and they end up with more dependencies. Worse, the classic patterns compose poorly: hierarchical state machines are typically not straightforward extensions.
Here I present a composable pattern for pure state machiness with effects. The pure-impure separation is inspired by _[functional core, imperative shell](https://www.destroyallsoftware.com/talks/boundaries)_; the modeling of novelty and effect by [Elm](http://elm-lang.org); the hierarchical composition by [Harel statecharts](http://www.inf.ed.ac.uk/teaching/courses/seoc/2005_2006/resources/statecharts.pdf).
The solution is implemented in Swift because I'm particularly interested in a solution which makes sense in a fairly typical imperative context, and because Swift's types help illustrate the structure I'm suggesting.
## The basic types
The key idea is that states, transitions,and specifications of effect are modeled by a _pure value type_; a separate dumb object actuates those effects (as in Elm; or think of the traditional [Command pattern](https://en.wikipedia.org/wiki/Command_pattern)).
```swift
protocol StateType {
/// Events are effectful inputs from the outside world which the state reacts to, described by some data type.
/// For instance: a button being clicked, or some network data arriving.
associatedtype Event
/// Commands are effectful outputs which the state desires to have performed on the outside world. For
/// instance: showing an alert, transitioning to some different UI, etc.
associatedtype Command
/// In response to an event, a state may transition to some new value, and it may emit a command.
mutatingfunc handleEvent(event:Event)->Command?
// If you're not familiar with Swift, the mutation semantics here may seem like a very big red flag,
// destroying the purity of this type. In fact, because states have *value semantics*, mutation becomes
// mere syntax sugar. From a semantic perspective, any call to this method creates a new instance of
// StateType; no code other than the caller has visibility to the change; the normal perils of mutability
// do not apply.
/// State machines must specify an initial value.
static var initialState: Self { get }
// Traditional models often allow states to specific commands to be performed on entry or exit.
// We could add that, or not.
}
```
## A basic state machine with effects
Here's an example based on [A Pattern Language of Statecharts](http://industriallogic.net/patterns/P22.pdf), figure 3:
![figure 3](figure3.png)
```swift
/// First up, the StateType. Note that this type is isolated, pure, trivial to fully test with zero mocks/stubs, etc.
Now, if you have a lot of states, you might want to start grouping them into "child" state machines,especially if there's a significant shared semantic. Here I rephrase the example state machine hierarchically: an inner state machine and an outer one, following figure 6 from "A Pattern Language of Statecharts."
![figure 6](figure6.png)
Critically,*no other types* must change: the code above can use HierarchicalTurnstyleState asa drop-in replacement. StateTypes are closed under hierarchical composition. This is a natural consequence of the data-oriented design, which takes advantage of the simpler fact that enums are closed over carrying instances of other enums asan associated value,and that functions are closed over calling other functions. Nothing more complicated is required.
```swift
enum HierarchicalTurnstyleState: StateType {
case Functioning(FunctioningTurnstyleState)
case Broken(oldState: FunctioningTurnstyleState)
enumEvent{
case InsertCoin(value:Int)
case AdmitPerson
case MachineDidFail
case MachineRepairDidComplete
}
typealiasCommand=FunctioningTurnstyleState.Command // Of course, this could be some more complicated composition.
// Now somewhat simpler because the turnstyle can't be broken.
switch (self, event){
case(.Locked(let credit),.InsertCoin(let value)):
letnewCredit= credit + value
if newCredit >=TurnstyleState.farePrice {
self=.Unlocked
return.OpenDoors
}else{
self=.Locked(credit: newCredit)
}
case(.Locked,.AdmitPerson):
return.SoundAlarm
case(.Unlocked,.AdmitPerson):
self=.Locked(credit:0)
return.CloseDoors
default:
break // or maybe throw, etc
}
returnnil
}
}
// Conversion between the layers. You could approach this a variety of ways.
extension FunctioningTurnstyleState.Event{
init?(_ event:HierarchicalTurnstyleState.Event){
switch event {
case.InsertCoin(let value):
self=.InsertCoin(value: value)
case.AdmitPerson:
self=.AdmitPerson
case.MachineDidFail,.MachineRepairDidComplete:
returnnil
}
}
}
```
## Orthogonal state machines
The hierarchical state machines introduced above were always alternations: the parent machine is always in a state of one of its child machines. But instead the composition might be a Cartesian product,where the parent runs the child machines simultaneously, and its effective state is a*tuple* of the child states.
The relationship between this kind of composition (orthogonal) and the earlier kind of composition (alternative) is the same asthe relationship between product types (structs, tuples) and sum types (enums),so we see that reflected in the declaration below.
For instance, a keyboard might have two different state machines running simultaneously:one handles the toggling behavior of the main keypad's caps lock key and its impact on keypad behavior; the other handles the same thing for the numpad and num lock.
// In some orthogonal state machines, certain events will want to be dispatched to both children. We'd need a somewhat more complex structure for that.
// But hey, I can now define the same KeyboardState interface as an Adapter on OrthogonalState. Not that this is particularly cleaner, but it does contain less domain knowledge:
The use of `enum`s in this pattern is not terribly idiomatic.In a fundamentally imperative language, we usually describe events with methods.Unfortunately, methods aren't asflexible in use-vs-reference asenums, so state machines defined in this waydon't compose quite so cleanly.
Another issue with using functions to specify events is that they make the machine harder to "read" by emphasizing transitions over states.Compare this definition of TurnstyleState to the initial one; I argue it's much harder to picture the boxes-and-diagrams graph.(note that it's impossible to implement `OrthogonalStateType` on top of this structure)
All those places where I wrote "or throw?"... can we do better?
One key issue is that event handlers are fairly indiscriminate.If we're listening to events from somehardware sensor, those events are going to arrive regardless of what our state is, so we have to branch somewhere to decide whether to handle it or not.Right now, we branch in `handleEvent`.If individual states had their own types (only some of which included certain events), we'd just kick the branch up to whatever component dispatches events to states.
So a rigorous solution must meaningfully affect dispatch of the input events which lead to state changes.One solution (similar to Elm's) would be to allow states to (directly or indirectly) specify which upstream subscriptions are actually active. Those specifications could include enough type information to directly enforce that states only receive events with corresponding transitions.
I'll save a detailed treatment on this topic for another day.
## Acknowledgements
My thanks to Colin Barrett, Justin Spahr-Summers, Rob Rix, and Chris Eidhoff for useful feedback on this draft.