docs: def concatenate(*fsms):
  		'''
  			Concatenate arbitrarily many finite state machines together.
  		'''
  		alphabet = set().union(*[fsm.alphabet for fsm in fsms])
  
  		def connect_all(i, substate):
  			'''
  				Take a state in the numbered FSM and return a set containing it, plus
  				(if it's final) the first state from the next FSM, plus (if that's
  				final) the first state from the next but one FSM, plus...
  			'''
  			result = {(i, substate)}
  			while i < len(fsms) - 1 and substate in fsms[i].finals:
  				i += 1
  				substate = fsms[i].initial
  				result.add((i, substate))
  			return result
  
  		# Use a superset containing states from all FSMs at once.
  		# We start at the start of the first FSM. If this state is final in the
  		# first FSM, then we are also at the start of the second FSM. And so on.
  		initial = set()
  		if len(fsms) > 0:
  			initial.update(connect_all(0, fsms[0].initial))
  		initial = frozenset(initial)
  
  		def final(state):
  			'''If you're in a final state of the final FSM, it's final'''
  			for (i, substate) in state:
  				if i == len(fsms) - 1 and substate in fsms[i].finals:
  					return True
  			return False
  
  		def follow(current, symbol):
  			'''
  				Follow the collection of states through all FSMs at once, jumping to the
  				next FSM if we reach the end of the current one
  				TODO: improve all follow() implementations to allow for dead metastates?
  			'''
  			next = set()
  			for (i, substate) in current:
  				fsm = fsms[i]
  				if substate in fsm.map and symbol in fsm.map[substate]:
  					next.update(connect_all(i, fsm.map[substate][symbol]))
  			if len(next) == 0:
  				raise OblivionError
  			return frozenset(next)
  
  		return crawl(alphabet, initial, final, follow).reduce()