This repository contains examples of making invalid states non-representable (at compile time), using the following toy problem
Imagine a small base on the Moon with an astronaut living in it. They could put on or take off their space suite, open the lock doors or shut them, leave the base and enter it (come back), and while they are out, they could take a regolith sample, which goes into a special pocket on the suit. The pocket could later be emptied. They could also moan about their life when nobody could hear them.
We model this as a module/library/class/compilation unit (as suitable for your language of choice) called Moonbase that implements the following functions (choose camelCased or under_scored identifiers as you see fit):
begin_day-- starts a new day, with an astronaut inside, without a suit, and with the lock closed. It could be a class constructor or a similar initialization mechanism.open_lockclose_lockspacesuit_onspacesuit_offleave_baseenter_basemoantake_sampleempty_pocketend_day-- end the day on the Moonbase. It could be a class destructor or a similar wind-down mechanism.
For simplicity, these functions would print out a message to stdout, and do nothing else. In terms of behaviour, this Python implementation is sufficient and complete:
Simplest Python implementation
class Moonbase:
def __init__(self):
print ("DAY BEGINS")
def open_lock(self):
print ("OPENING LOCK")
def close_lock(self):
print ("CLOSING LOCK")
def spacesuit_on(self):
print ("SPACESUIT ON")
def spacesuit_off(self):
print ("SPACESUIT OFF")
def leave_base(self):
print ("LEAVING BASE")
def enter_base(self):
print ("ENTERING BASE")
def moan(self):
print (". o O (Ugh! Damn moon! I am too old for this!)")
def take_sample(self):
print ("TAKING REGOLITH SAMPLE")
def empty_pocket(self):
print ("EMPTYING POCKET")
def end_day(self):
print ("DAY ENDS")If you want these functions to take additional arguments or return some values, you are free to do this - do whatever is idiomatic/natural in your chosen language.
Additionally, there are a bunch of rules that we want to enforce (at compile time!) which determine when a particular function can or cannot be called.
- You can only put the suit on if it is not already on
- You can only remove the suit if it is already on
- You can open the lock only if it is closed
- You can close the lock only if it is open
- You have to have the suit on to open the lock
- Suit pockets should be empty to put it on (otherwise regolith sample can puncture the suit)
- You cannot take the suit off while outside
- You cannot take the suit off when the lock is open
- You cannot take the suit off when its pockets are full (otherwise regolith sample can puncture the suit)
- You can only leave base when you are inside, with the suit on, and with the lock open
- If you go outside, you always need to take a regolith sample. So you can only enter the base with the suit's pocket full and lock open
- The astronaut should never moan about life on the moon when in the suit - it will be recorded and will reflect poorly on them
- Taking regolith samples could only be done outside. It creates a lot of dust, so it should only be done with the lock closed. The suit pocket should be empty
- Suit pocket could be emptied whenever. If done outside, it means that the astronaut is now without a sample, and might need to procure one to re-enter the base
- At the end of the day, the astronaut should be inside the base, with the suit off, and the lock closed
We can add some extra state to the naive Python implementation above and use it to enforce those rules (at runtime):
Python implementation that enforces rules via assert()
class Moonbase:
lockOpen = False
suitOn = False
isOutside = False
pocketsFull = False
def __init__(self):
print ("DAY BEGINS")
def open_lock(self):
assert(not self.lockOpen)
assert(self.suitOn)
print ("OPENING LOCK")
self.lockOpen=True
def close_lock(self):
assert(self.lockOpen)
print ("CLOSING LOCK")
self.lockOpen=False
def spacesuit_on(self):
assert(not self.suitOn)
assert(not self.pocketsFull)
print ("SPACESUIT ON")
self.suitOn=True
def spacesuit_off(self):
assert(self.suitOn)
assert(not self.isOutside)
assert(not self.lockOpen)
assert(not self.pocketsFull)
print ("SPACESUIT OFF")
self.suitOn=False
def leave_base(self):
assert(self.lockOpen)
assert(self.suitOn)
assert(not self.isOutside)
print ("LEAVING BASE")
self.isOutside=True
def enter_base(self):
assert(self.isOutside)
assert(self.pocketsFull)
assert(self.lockOpen)
print ("ENTERING BASE")
self.isOutside=False
def moan(self):
assert(not self.suitOn)
print (". o O (Ugh! Damn moon! I am too old for this!)")
def take_sample(self):
assert(self.isOutside)
assert(not self.lockOpen)
assert(not self.pocketsFull)
print ("TAKING REGOLITH SAMPLE")
self.pocketsFull=True
def empty_pockets(self):
assert(self.pocketsFull)
print ("EMPTYING POCKETS")
self.pocketsFull=False
def end_day(self):
assert(not self.isOutside)
assert(not self.lockOpen)
assert(not self.suitOn)
assert(not self.pocketsFull)
print ("DAY ENDS")If you think this looks a bit like a state machine, you are right! However, let's not focus on this too much—this is not about rewriting the problem as a state machine or implementing an equivalent state machine. We are focusing on the users of our API first and foremost, not on its internal implementation.
Everything on the Moon is expensive, including runtime errors. Can we enforce the rules entirely at the compile time, so that the program that violates one of the rules above simply cannot be written?
Here are the implementations in:
Can you contribute an implementation in your favourite language? If you can, please do! If we already have your favourite language covered, but you feel that you can do better, please contribute alternative implementation.
Fork this project, create <language>/<your GitHub id>/, place your implementation there, and submit a PR. If possible, please make sure that your application can be built and run without installing a full toolchain for your language -- Docker is a great help here if it could be used.
Different languages would demand slightly different formulations of the task. But lets try to keep the common baseline:
- Your implementation should provide module/library/class Moonbase
- ... that provides a dozen API functions/methods/.. described above
- ... that end-users could call in their code in whatever order they see fit
- ... and incorrect state transitions are rejected at compile time.
You can treat OCaml implementation in ocaml/adept as a reference implementation, and
test against it. OCaml implementation also includes a non-exhaustive list of examples that should not compile.