A road to state machines
Me
- Margarita, @rita_krutikova
- 🇺🇦 Ukraine → Sweden, Gothenburg 🇸🇪 🌧
- React + Typescript, Reason,
- GraphQL + Apollo
- F#, C#
- Functional programming, F#, ReasonML
- dev.to/margaretkrutikova
👩💻
🏠
Before we start
- Assumes some basic knowledge of functional concepts
- variants (DU)
- pattern matching
- pure functions/immutability
- Examples in ReasonML
- Beginner intro to state machines
Agenda
- A few words about state machine
- Pack your bag, we are driving out
- State machine recap
- Benefits
- Recognising state machines
Machine?
- mathematical concept
- design pattern in software development
-
domain modelling
-
represent data and behaviour in code
-
A road to state machine #1
type user = {
isLoggedIn: bool,
};
type userData = {
userName: string,
email: string,
};
type user = {
isLoggedIn: bool,
userData: option(userData),
};
type user = {
isLoggedIn: bool,
userData: option(userData),
tempUserName: option(string),
};
A road to state machine #2
let setTempUserName = (tempUserName, user) =>
{...user, tempUserName: Some(tempUserName)};
let logIn = (userData, user) => {
...user,
isLoggedIn: true,
userData: Some(userData),
};
let logOut = user => {...user, isLoggedIn: false};
let postQuestion = (question, user) => {
let authorName =
switch (user.tempUserName, user.userData) {
| (Some(tempName), _) => tempName
| (_, Some(userData)) => userData.userName
| _ => failwith("You can't post questions!")
};
post(question, authorName)
};
A road to state machine #3
A road to state machine #3
let setTempUserName = (tempUserName, user) =>
{...user, tempUserName: Some(tempUserName)};
Is not allowed when logged-in! 🐞
#1 What, I just set temporary name while being logged-in! Now I am stuck with it! 😠
A road to state machine #3
let logIn = (userData, user) => {
...user, isLoggedIn: true, userData: Some(userData),
};
Temporary name is still there! 🐞
#2 Wait, I logged in, why does it show my temporary name? 🤨
A road to state machine #3
How about resetting userData? 🐞
let logOut = user => {...user, isLoggedIn: false};
#3 Haha, I logged out and could still post with my user name! 😆
What went wrong?
A road to state machine #4
type user = {
isLoggedIn: bool,
userData: option(userData),
tempUserName: option(string),
};
- Many invalid combinations
- User can only be anonymous OR temporary OR logged in
One of the states at a time!
What went wrong?
A road to state machine #4
- States are implicit
- Update properties that belong to one state?
let logOut = user => {...user, isLoggedIn: false};
- User inputs are implicit too
- No connection between the states and inputs!
A road to state machine #5
type user =
| Anonymous
| TemporaryUser(string)
| LoggedInUser(userData)
type userInput =
| SetTemporaryName(string)
| LogIn(userData)
| LogOut;
Explicit state and inputs
A road to state machine #5
nextState = transition(currentState, input)
combinations = states * inputs
Transition function
A road to state machine #6
Visualising
Anonymous
Temporary user
Logged in user
SetTempName
Log in
Log in
Log out
A road to state machine #6
Visualising
A road to state machine #7
let transition = (user, input) => {
switch (user) {
| Anonymous =>
switch (input) {
| SetTemporaryName(name) => TemporaryUser(name)
| LogIn(data) => LoggedInUser(data)
| LogOut => user
}
| TemporaryUser(_) =>
switch (input) {
| LogIn(data) => LoggedInUser(data)
| SetTemporaryName(_) | LogOut => user
}
| LoggedInUser(_) => ...
};
};
Implementing transition
A road to state machine #7
let transition = (user, input) => {
switch (user, input) {
| (Anonymous, SetTemporaryName(tempName)) => TemporaryUser(tempName)
| (Anonymous, LogIn(userData)) => LoggedInUser(userData)
| (TemporaryUser(_), LogIn(userData)) => LoggedInUser(userData)
| (LoggedInUser(_), LogOut) => Anonymous
| _ => user
};
};
Avoid _ pattern match
A road to state machine #8
Use as reducer
[@react.component]
let make = () => {
let (state, dispatch) =
React.useReducer(transition, Anonymous);
<button onClick={_ => dispatch(LogOut)}>
{React.string("Log out")}
</button>;
};
A road to state machine #9
Use in your UI
<div>
{(switch (state) {
| Anonymous => "You can't post questions."
| TemporaryUser(userName) =>
"Post with your temporary name: " ++ userName
| LoggedInUser({userName}) =>
"Post with your user name:" ++ userName
}) |> React.string}
</div>;
State machine Recap #1
- States - variant (discriminated union)
- Only one state at a time
- Start in initial state
- Input (event) - variant (DU)
- Transition - new state from current state + input
- It is just a design pattern (no lib required)
- Can be introduced just in a small part of your app
- Can be used in any language/environment
- Better support in functional languages
- Variants/pattern matching
- Compiler warnings - exhaustive match
State machine Recap #2
Benefits
- Protect against invalid states
- Protect against invalid state transitions
- Account for all edge cases (trivial...)
- Better understand the domain
- Discover potential problems in spec of business rules
- IT IS EASY - follow the pattern, focus on the behaviour
- Fun to visualize
Use cases
- Boolean flags
- isValid, isLoading, isError,
- Options
- error, validResponse etc.
- A flood of If-checks
type apiRequest('t) = {
isLoading: bool,
data: option('t),
error: option(string),
};
Thank you! ❤️
Questions?
A road to state machines
By margaretkru
A road to state machines
- 389