Moving Forward, Turning, Leaving a Trail
The first Logo turtle lived in 1967, at MIT’s AI Lab. Seymour Papert and his colleagues connected a mechanical robot to a PDP-1 — an actual physical turtle that rolled across paper, pen attached, following commands. Forward 100. Right 90. Forward 100. The turtle drew a corner. Children who couldn’t articulate Cartesian coordinates could direct this creature through space, and geometry emerged from its trail.
By the 1980s, the turtle had migrated onto screens in classrooms everywhere, a triangular cursor leaving phosphor traces. The abstraction is absurdly simple: the turtle has a position, an angle, and a pen state (up or down). It understands just a handful of verbs. Yet from these verbs come spirals, stars, fractals, and — if you squint at my sashiko practice cloth — wave patterns.
Consider the seigaiha wave I’ve been stitching. Each arc is a curve, yes, but approximated as short segments with small turns between them. Forward a stitch-length, turn slightly, forward another. The needle accumulates fabric like a turtle accumulating distance. The thread trail is the pen-down trace.
module Turtle where
type State = (Float, Float, Float) -- x, y, angle in degrees
forward :: Float -> State -> State
forward d (x, y, a) = (x + d * cos r, y + d * sin r, a)
where r = a * pi / 180
turnRight :: Float -> State -> State
turnRight deg (x, y, a) = (x, y, a - deg)
arc :: Int -> Float -> State -> [State]
arc steps radius start = scanl (.) id (replicate steps move) <*> [start]
where move = forward (2 * pi * radius / fromIntegral steps) . turnRight (360 / fromIntegral steps)
Haskell’s approach treats position as immutable data, each command returning a new state. The arc function chains small forward-and-turn moves, building a list of positions — a stitch path.
struct Turtle { x: f64, y: f64, angle: f64 }
impl Turtle {
fn forward(&mut self, d: f64) {
self.x += d * self.angle.to_radians().cos();
self.y += d * self.angle.to_radians().sin();
}
fn right(&mut self, deg: f64) { self.angle -= deg; }
fn arc(&mut self, steps: u32, radius: f64) -> Vec<(f64, f64)> {
let step_len = 2.0 * std::f64::consts::PI * radius / steps as f64;
let turn = 360.0 / steps as f64;
(0..steps).map(|_| { self.forward(step_len); self.right(turn); (self.x, self.y) }).collect()
}
}
Rust mutates in place — the turtle crawls forward, dragging its state along. Same geometric result, different relationship with time. One traces history, the other rewrites the present.
The 3:2 stitch ratio I keep failing to maintain is a turtle parameter: forward(1.5 * gap) for the visible thread, forward(gap) for the space between. The intersection gaps where threads shouldn’t share holes? That’s pen-up: pen_up(); forward(crossing_width); pen_down();
Papert called this body-syntonic learning — you understand the turtle because you are the turtle, imagining yourself walking the path. Sashiko is the same. You don’t calculate where stitches land; you feel the rhythm of stab-gather-pull and let the geometry emerge from motion. The needle knows where it’s going. You’re just helping it turn.