Steve's Roc Apps roc-apps on GitHub

Safari Ride Chapters

Chapter 6

Functions

Every chapter from here on is full of Roc functions, so here we learn to read one, piece by piece. A Roc function takes its arguments and returns one value. The darkness_at function returns how dark the sky is at a step of the ride:

Sky.roc
## How dark the sky is at `clock`, from 0 (day) to 1 (night).
darkness_at : Clock -> Fraction
darkness_at = |clock| {
	height = SunPath.height_at(clock)
	if height >= SunPath.height_when_set {
		darkness_while_up(height)
	} else {
		darkness_after_set(height)
	}
}

A function is a value §

In Roc, a function is an ordinary value, like a number. Its code is an anonymous function, and = gives it a name. Roc calls a named value a constant: once named, it keeps its value. Inside a function's body, the constants are named again on each run, so each run has its own. Here's the function, piece by piece:

Code Term What it says
## doc comment Describes the code under it.
darkness_at : Clock -> Fraction type annotation The function's name, a colon, and its type. darkness_at takes a Clock, which counts the ride's steps, and returns a Fraction, a number from 0 to 1.
-> thin arrow Separates what the function takes from what it returns.
darkness_at = constant = names the function.
|clock| anonymous function The function itself starts here.
clock argument What the function is given.
{ and } block Names for values, then one last expression. A block is an expression too, and its value is that last expression's value.
height = constant A name for a value, which the lines below it use.
SunPath.height_at(clock) function call Calls height_at from the SunPath module, with clock as its argument.
if and its two branches if expression The block's last expression, so its value is what the function returns: one of the two calls, picked by whether the sun is still up. SunPath.height_when_set, the sun's height once it has set, is a constant from the same module. darkness_while_up and darkness_after_set are functions further down in the Sky module, outside its type's braces, and a module calls those by their bare names.

Because a function is a value, the chapters ahead pass functions to other functions and keep them in records' fields.

A body is one expression §

A function's body is always one expression. A block is one kind, and a short body can be a plain expression, written straight after the |. The Distance module's metres turns a number of metres, an F64, into a length:

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

Inside the body, metres is the argument. The function sits inside Distance's braces, so it goes by Distance.metres, and the bare name is free for the argument. Distance.(metres) wraps the number as a Distance. So a one-line function and a ten-line one have the same shape: arguments in, one expression's value out.

An annotation states the contract §

An annotation states what a function takes and what it returns, with commas between the things it takes:

Sky.roc
color_at_darkness : RgbColor, Fraction -> RgbColor

color_at_darkness takes an RgbColor and a Fraction, and returns an RgbColor. The compiler holds every call to that contract. The book often shows a function's annotation alone, until its body's turn comes, because the annotation already says what goes in and what comes out.

A method belongs to its type §

Many calls in the book put a value first, as in SunPath.height_at(clock).minus(FarHeight.zero). That's a method call. A method is a function that belongs to a particular type. Here's a function built from method calls, which returns how warm the horizon's color is, from how near the sun stands to the horizon:

Sky.roc
## How warm the horizon is at `clock`: most while the sun touches it, none
## once the sun is `warmth_reach` above or below.
horizon_warmth_at : Clock -> Fraction
horizon_warmth_at = |clock| {
	from_horizon = SunPath.height_at(clock).minus(FarHeight.zero).abs()
	Fraction.clamp(from_horizon.ratio_to(warmth_reach)).complement()
}
Code Term What it says
SunPath.height_at(clock) function call Returns the sun's height, a FarHeight.
.minus(FarHeight.zero) method call FarHeight has a minus method. The value before the dot is its first argument, so this is FarHeight.minus(SunPath.height_at(clock), FarHeight.zero).
.abs() method call A method whose one argument is the value before the dot. minus returns a FarRise, the rise from one height to another, so this calls FarRise.abs.
from_horizon.ratio_to(warmth_reach) method call Divides one FarRise by another: how far the sun stands from the horizon, as a share of warmth_reach, a constant rise further down in the module.
Fraction.clamp(…).complement() function call, then method call clamp keeps a number between 0 and 1, as a Fraction, and complement returns 1 minus it.

A type's methods are the functions between its .{ and }, as Distance's were. The compiler infers every value's type, so it resolves each method call to that type's own function when the program compiles. Even >= in darkness_at calls a method, is_gte.

Every function has this shape §

A function is a value, its body is one expression, its annotation states its contract, and its methods belong to its types. Every function from here on is built the same way. The next chapter starts on the things beside the road.