Golang: recursive types v2
In the previous post, I wrote about recursive types: maps, slices, structs, and channels. Structs, maps, and slices show up in real data.
A recursive channel is mostly an interview topic.
Let’s continue.
Did you know this signature is valid?
type Func func() FuncA function type that refers to itself.
How do you use that in real code? Is it useful?
Yes, if you don’t like if-else spaghetti.
In the previous post, I mentioned PBEM (Play-by-Email) games
and noticed that each step had no World. Let’s fix that.
Get Mike’s stories in your inbox
Join Medium for free to get updates from this writer.
First, the world and a step over it:
type World struct {
TurnID int
HasWinner bool
NextPlayer string
}
type StepFn func(*World) StepFnThen, a few steps:
// waiting for the email move
func waitForEmailStep(w *World) StepFn {
fmt.Printf("[Turn %d]: Waiting for %s's email move...\n", w.TurnID, w.NextPlayer)
return processMoveStep
}
func processMoveStep(w *World) StepFn {
fmt.Println("[Engine]: Calculating ... etc.")
if w.TurnID >= 3 {
w.HasWinner = true
return endGameStep
}
w.TurnID++
if w.NextPlayer == "Player #1" {
w.NextPlayer = "Player #2"
} else {
w.NextPlayer = "Player #1"
}
return sendNotificationsStep
}
// jump back to waiting
func sendNotificationsStep(w *World) StepFn {
fmt.Printf("[Email]: Sending new save game file to %s ...\n", w.NextPlayer)
return waitForEmailStep
}
// base case: nil breaks the loop
func endGameStep(w *World) StepFn {
fmt.Println("[Game Over]: FINAL RESULTS.")
return nil
}Nothing magic here. These are just the actions the game needs in order to progress.
And the driver:
func main() {
world := &World{TurnID: 1, NextPlayer: "Player #1"}
state := waitForEmailStep
for state != nil {
state = state(world)
}
}The useful part is isolation, and how small the control flow stays.
There is no package-level state. The only shared state is the World itself.
You can easily recombine the functions or insert a new step. New game release!
Full code is here.
This is a more or less real example.
For the same idea in the standard library,
see text/template/parse/lex.go where Rob Pike uses type stateFn func(*lexer) stateFn to build a clean state machine for the template lexer.
Happy coding!



