Chapter 20
Immutable values: a flip book
The first half of the book held one page still, step 1,529, and built it part by part. Now the pages turn. The picture below holds a real ride. The bar is the whole route, with the rider and the truck as two dots on it, and the lines of text read out the clock, the rider and the truck. The figure under the bar is the rider seen from behind, with the lean drawn three times as steep. Press play and let it run for a moment. Press play again to stop it, then press the step-back button a few times.
The ride steps back one moment at a time, as easily as it went forward. A Roc value stays exactly as it was made, and the ride changes from start to finish. A flip book shows how both hold.
The “One page” chapter called the ride a flip book. Each page is a still drawing, and the motion comes from the flipping. In Safari Ride, each page is a moment, a value of the RideInstant type, and one function makes the next one:
next_or_restart : Route, RideInstant -> [Continued(RideInstant), Restarted(RideInstant)]
It takes the road and a moment, and returns a tag union written in place: the next moment, tagged Continued, or the ride's first moment again, tagged Restarted, once the rider reaches the end of the road. Either way, it returns a new moment and leaves the old one as it was.
The platform does the flipping. Thirty times a second it asks the app for its next state, and while the ride plays, the app moves on to the next moment through next_or_restart. Then the platform paints that moment's picture, which FrameView.frame_view_at works out from the moment and the road. The picture above flips much faster than that, to cover more road. The same moment always gives the same picture, so you can hold any page still and study it, as the first half of the book held step 1,529.
Turning back §
Every old moment stays as it was made, so the ride keeps the ones since it last started, and stepping back turns to an earlier page. Here's the function behind the step-back button you just pressed:
## The tour a step back. At the ride's start there is nothing behind it,
## so it stays there.
step_back : Tour -> Tour
step_back = |Tour.(tour)|
match tour.past_instants.last() {
Ok(previous_instant) => {
past_instants = tour.past_instants.drop_last(1)
Tour.({ ..tour, instant: previous_instant, past_instants })
}
Err(ListWasEmpty) => Tour.(tour)
}
A Tour holds the moment on show, and a list of the moments before it. Tour.(tour) opens the tour to read its fields. step_back takes the last moment off that list and puts it on show, with a record update of those two fields. The old tour stays as it was. At the ride's start the list is empty, so last returns Err(ListWasEmpty), and the tour stays where it is.
What a moment holds §
Here, once more, is everything a moment holds:
# ...
RideInstant := {
rider : Rider,
truck : TruckJourney.TruckProgress,
clock : Clock,
encounter_plan : EncounterPlan,
}.{
# ...
That's four things: the rider, the truck, the clock, and the plan for when the cat or the bird ahead starts to move. The ride carries those four from step to step, and derives everything else in a frame from them and the road.