Skip to content

Instantly share code, notes, and snippets.

@Alex-Ozun
Last active March 22, 2024 10:03
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Alex-Ozun/9669bb1b02be1f63cb509ea859869c31 to your computer and use it in GitHub Desktop.
Save Alex-Ozun/9669bb1b02be1f63cb509ea859869c31 to your computer and use it in GitHub Desktop.
Typestate in Swift
enum Parked {}
enum Driving {}
enum Gaming {}
private class EngineSystem {
static var shared = EngineSystem()
private init() {}
func start() {/**/}
func accelerate() { /* Uses gas pedal input to accelerate the real car */ }
func brake() { /* Uses brake pedal input to deccelerate the real car */ }
}
private class InfotainmentSystem {
static var shared = InfotainmentSystem()
private init() {}
func launchRacingGame() { /**/ }
func accelerate() { /* Uses gas pedal to accelerate in Beach Buggy Racing game */ }
func brake() { /* Uses brake pedal to deccelerate in Beach Buggy Racing game */ }
}
struct Tesla<State>: ~Copyable {
private let engineSystem: EngineSystem
private let infotainmentSystem: InfotainmentSystem
private init(engineSystem: EngineSystem, infotainmentSystem: InfotainmentSystem) {
self.engineSystem = engineSystem
self.infotainmentSystem = infotainmentSystem
}
}
extension Tesla where State == Parked {
init() {
self.init(
engineSystem: EngineSystem.shared,
infotainmentSystem: InfotainmentSystem.shared
)
}
consuming func launchRacingGame() -> Tesla<Gaming> {
Tesla<Gaming>(
engineSystem: engineSystem,
infotainmentSystem: infotainmentSystem
)
}
consuming func drive() -> Tesla<Driving> {
Tesla<Driving>(
engineSystem: engineSystem,
infotainmentSystem: infotainmentSystem
)
}
}
extension Tesla where State == Driving {
consuming func park() -> Tesla<Parked> {
Tesla<Parked>(
engineSystem: engineSystem,
infotainmentSystem: infotainmentSystem
)
}
func accelerate() {
engineSystem.accelerate()
}
func brake() {
engineSystem.brake()
}
}
extension Tesla where State == Gaming {
consuming func exitRacingGame() -> Tesla<Parked> {
Tesla<Parked>(
engineSystem: engineSystem,
infotainmentSystem: infotainmentSystem
)
}
func accelerate() {
infotainmentSystem.accelerate()
}
func brake() {
infotainmentSystem.brake()
}
}
func main() {
var parked = Tesla<Parked>()
let gaming = parked.launchRacingGame()
gaming.accelerate() // accelerating in Beach Buggy Racing game
gaming.brake() // braking in Beach Buggy Racing game
parked = gaming.exitRacingGame()
let driving = parked.drive()
driving.accelerate() // accelerating the real car
driving.launchRacingGame() // ❌ ERROR: can't game while driving
}
enum TeslaState: ~Copyable {
case driving(Tesla<Driving>)
case parked(Tesla<Parked>)
case gaming(Tesla<Gaming>)
init() { self = .parked(Tesla<Parked>()) }
mutating func launchRacingGame() {
switch consume self {
case let .driving(driving):
self = .driving(driving)
case let .parked(parked):
self = .gaming(parked.launchRacingGame())
case let .gaming(gaming):
self = .gaming(gaming)
}
}
mutating func accelerate() {
switch consume self {
case let .driving(driving):
driving.accelerate()
self = .driving(driving)
case let .parked(parked):
self = .parked(parked)
case let .gaming(gaming):
gaming.accelerate()
self = .gaming(gaming)
}
}
mutating func brake() {/**/}
mutating func drive() {/**/}
mutating func park() {/**/}
}
func main2() {
var tesla = TeslaState()
tesla.drive() // Tesla<Driving>
tesla.accelerate() // accelerating a real car
tesla.launchRacingGame() // does nothing
tesla.park() // Tesla<Parked>
tesla.launchRacingGame() // Tesla<Gaming>
tesla.accelerate() // accelerating in Beach Buggy Racing
}
import Foundation
enum Locked {}
enum Unlocked {}
struct Turnstile<State>: ~Copyable {
private(set) var coins: Int
private init(coins: Int) {
self.coins = coins
}
}
extension Turnstile where State == Locked {
init() {
self.init(coins: 0)
}
consuming func insertCoin() -> Turnstile<Unlocked> {
Turnstile<Unlocked>(coins: coins + 1)
}
}
extension Turnstile where State == Unlocked {
consuming func push() -> Turnstile<Locked> {
Turnstile<Locked>(coins: coins)
}
}
func illegalOperationsAreImpossible() {
var locked = Turnstile<Locked>()
var unlocked = locked.insertCoin()
// Uncomment to see errors
// unlocked.insertCoin() // ❌ ERROR: Can't insert coin when Unlocked
// locked.push() // ❌ ERROR: Can't push when Unlocked
}
func cannotReuseState() {
var locked = Turnstile<Locked>()
var unlocked = locked.insertCoin()
// unlocked = locked.insertCoin() // ❌ ERROR: Can't insert coin again
}
func strictOrderOfOperations() {
var locked = Turnstile<Locked>()
.insertCoin()
.push()
.insertCoin()
.push()
print(locked.coins) // 2
locked
.insertCoin()
// .insertCoin() // ❌ ERROR: can't insertCoin again
.push()
// .push() // ❌ ERROR: can't push again
}
enum TurnstileState: ~Copyable {
case locked(Turnstile<Locked>)
case unlocked(Turnstile<Unlocked>)
init() {
self = .locked(Turnstile<Locked>())
}
mutating func coins() -> Int {
let coins: Int
switch consume self {
case let .locked(locked):
coins = locked.coins
self = .locked(locked)
case let .unlocked(unlocked):
coins = unlocked.coins
self = .unlocked(unlocked)
}
return coins
}
mutating func insertCoin() {
switch consume self {
case let .locked(locked):
let unlocked = locked.insertCoin()
self = .unlocked(unlocked)
case let .unlocked(unlocked):
self = .unlocked(unlocked)
}
}
mutating func push() {
switch consume self {
case let .locked(locked):
self = .locked(locked)
case let .unlocked(unlocked):
let locked = unlocked.push()
self = .locked(locked)
}
}
}
func combinedInterface() {
var turnstile = TurnstileState()
turnstile.insertCoin() // unlocked
turnstile.insertCoin() // does nothing
turnstile.push() // locked
turnstile.push() // does nothing
print(turnstile.coins()) // 1
}
// Example of a non-deterministic transition
extension Turnstile where State == Locked {
enum TransitionResult: ~Copyable {
case locked(Turnstile<Locked>)
case unlocked(Turnstile<Unlocked>)
}
consuming func tryInsertCoin(coinDiameter: Decimal) -> TransitionResult {
if coinDiameter <= 5 {
return .unlocked(Turnstile<Unlocked>(coins: coins + 1))
} else {
return .locked(self)
}
}
}
extension TurnstileState {
mutating func tryInsertCoin(coinDiameter: Decimal) {
switch consume self {
case let .locked(locked):
let result = locked.tryInsertCoin(coinDiameter: coinDiameter)
switch consume result {
case let .locked(locked):
self = .locked(locked)
case let .unlocked(unlocked):
self = .unlocked(unlocked)
}
case let .unlocked(unlocked):
self = .unlocked(unlocked)
}
}
}
func nonDeterministicTransition() {
var turnstile = TurnstileState()
turnstile.tryInsertCoin(coinDiameter: 10) // still locked
turnstile.tryInsertCoin(coinDiameter: 2) // unlocked
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment