Steve's Roc Apps roc-apps on GitHub

Safari Ride Chapters

Chapter 5

Types

Types are at the heart of every Roc program. A type says what a value holds, and the compiler checks every value against its type. That gives this book its rule: get the types right, and everything else follows. A type that holds just the values that make sense leaves each function a plain job, and the compiler checks the rest. So a good way to meet a Roc program is to read its types.

This book shows Safari Ride's types first, and they reach every part of the program, from a single number to the whole ride. Types describe the structure of a program's data and the rules it keeps, and each stop shows one way they do it.

Distance pins down a number §

Safari Ride's types are built from numbers. A length and a speed are both an F64 underneath, so the ride gives each a type of its own. Here's the start of a length's:

Distance.roc, in Distance
Distance :: F64.{

	metres : F64 -> Distance
	metres = |metres| Distance.(metres)
	# ...

The :: makes Distance opaque: inside is an F64, and only the Distance module opens it. metres wraps a number of metres as a Distance. So a Distance is more precise than an F64: its type says what the number means, and the compiler keeps a length apart from a speed or any other number. The “Opaque types: the clock” chapter shows that precision catching a bug.

A billboard spells out its fields and its choices §

Step 1,529 of the ride, held still: the frame these types describe.

Every thing beside the road has a kind, and each kind is a type, such as Conifer for a tree. A cow is a Billboard, an emoji that stands on the grass, looking left or right:

Billboard.roc, in Billboard
Billboard :: {
	emoji : EmojiStill,
	height : Distance,
	facing : Billboard.Facing,
}.{

	## Which way a billboard faces on screen.
	Facing : [FacesLeft, FacesRight]
	# ...

Each line inside the first braces names a field and its type, so a billboard is an emoji, a height and a facing. A Billboard.Facing is one of two tags, FacesLeft or FacesRight. A tag is a name, and a type made of tags lists every value it holds. So the type spells out a billboard's structure, its three fields, and a rule it keeps: it faces one of exactly two ways.

Plain records lay out the road §

The World module holds the road's plan as a short list of stretches, called segments, and each segment's plan is a record of the SegmentPlan type:

World.roc
## One segment of the route as planned: its length and scenery, and what
## lives along it.
SegmentPlan : {
	length : Distance,
	scheme : Trees.Scheme,
	encounter : Segment.PlannedEncounter,
	pigs : World.PigPresence,
	bull : CowHerd.Bull,
}

Some types are plain data. A : gives this record type a short name, so any module can build a plan and read its fields. Here's the plan for the last stretch of road:

World.roc
## The route's last segment, which ends it.
last_plan : World.SegmentPlan
last_plan = {
	length: Distance.metres(300.0),
	scheme: RedGreen,
	encounter: CatCrosses,
	pigs: WithPigs,
	bull: WithBull,
}

After a value's name, the same : gives the value its type, and the lines after it define the value. The World.route value is the whole road, built from the plans once, before the ride starts. So plain records lay out the whole road, as data any module can build and read.

The whole route from above, one stretch for each plan. A mark shows where a cat crosses or a bird flies.

A type variable carries every kind §

The screen shows what a camera on the rider sees. Each thing takes three forms on its way there, and each form is a type. First, a thing has a place on the road:

Geom.roc
## An object on one segment of the road, `lift` off the ground, and what
## kind of object it is. A tree is placed once for the whole ride, a cow
## from its herd whenever something asks for it, and a cat, a tower or the
## truck anew for each frame.
## `Pipeline.sighting_of` turns it into a `Pipeline.Sighted` for a frame.
Placed(kind) : {
	road_point : Geom.RoadPoint,
	lift : Distance,
	kind : kind,
}

The kind in parentheses is a type variable: it stands for whatever type fills it. So a cow on the road is a Geom.Placed(Billboard), and one type serves every kind. Next comes the thing as the camera sights it:

Pipeline.roc
## An object in the rider's view, and what kind of object it is, for one
## frame.
Sighted(kind) : { spot : Spot, kind : kind }

A sighting keeps the kind, and adds a Spot: where the thing stands in the rider's view. Last comes the thing as Pipeline.rendering_of renders it:

Rendered.roc, in Rendered
Rendered :: { depth : Distance, sprite : Sprite }.{
# ...

A rendering holds the thing's depth, how far ahead of the camera it stands, and its sprite: the shapes that show it, in the order they're painted.

Sprite.roc, in Sprite
Sprite :: List(Shapes.Shape).{
# ...

A rendering drops the kind, so renderings of every kind share one type, and trees and cows meet in one list. So the type variable carries the kind as far as the trip needs it, and the rendering lets it go.

One record captures the whole ride §

One record holds the whole ride at one moment:

RideInstant.roc, in RideInstant
# ...
RideInstant := {
	rider : Rider,
	truck : TruckJourney.TruckProgress,
	clock : Clock,
	encounter_plan : EncounterPlan,
}.{
# ...

The := makes RideInstant nominal: a new type whose fields any module reads, a third way to name a type. Its four fields are everything the ride carries from one step to the next: the rider, the truck, the clock, and the plan for when the cat or the bird ahead starts to move. We derive everything else that changes from them, such as the sun's height from the clock. So one small record captures the whole ride. Motion is then one function from a moment to the next:

RideInstant.roc
next_or_restart : Route, RideInstant -> [Continued(RideInstant), Restarted(RideInstant)]

It returns one of two tags, written in place in square brackets, and each tag carries the next moment.

The tour keeps the past §

The app's model is one line:

SafariApp.roc
Model : { tour : Tour, motion : [Playing, Paused] }

motion is two tags written in place: the ride is playing or paused. The tour holds the route, the moment on show, and the moments before it:

Tour.roc, in Tour
# ...
Tour :: {
	route : Route,
	instant : RideInstant,
	past_instants : List(RideInstant),
}.{
# ...

Stepping back works because past_instants keeps those moments. So the feature starts in the type.

Types come first §

You've read three ways to name a type: : for a short name, :: for an opaque type, and := for a nominal one.

On the way, the types pinned down a number, spelled out a thing's fields and choices, laid out the road, carried every kind, captured the whole ride and kept its past. Each described the data's structure or a rule it keeps, and the same few kinds of type served the whole program. The next chapter takes one function apart, and then we start with things.