Lesson 02 · Functional Programming in Scala

Make behavior a value

Higher-order and polymorphic functions: stop duplicating the loop, inject the policy.

Tangible win

You will build one reusable isSorted function that can check ascending numbers, descending numbers, or another type without changing its loop. The behavior changes because the caller supplies a function.

Retrieve before reading

If the same function checks both ascending and descending readings, what should change: the loop or the rule that compares two neighboring values?

Reveal a strong answer

Change the comparison function. The traversal stays the same; the caller injects the ordering policy.

Functions can be data

In Lesson 01, the inputs were readings and the output was a number. Scala also lets us pass a function as an input or return one as an output. A function that does either is a higher-order function.

def applyTwice[A](f: A => A, value: A): A =
  f(f(value))

val addOne = (x: Int) => x + 1
applyTwice(addOne, 3) // 5

f is not a special command. It is an ordinary value with the type A => A. The function receives behavior from its caller, just as usageFor received readings from its caller.

Type vocabulary

Int => Int means “a function from Int to Int.” (A, A) => Boolean means “a function that receives two values of the same, still-unknown type and returns a decision.”

The reusable loop

Here is the important move: define the traversal once, then pass in the meaning of “in order.” In this version, ordered(a, b) returns true when a is allowed to appear before b.

def isSorted[A](as: Array[A], ordered: (A, A) => Boolean): Boolean = {
  @annotation.tailrec
  def go(n: Int): Boolean =
    if (n >= as.length - 1) true
    else if (!ordered(as(n), as(n + 1))) false
    else go(n + 1)

  go(0)
}

Notice what the loop does not know. It does not know whether the values are numbers, labels, timestamps, or ascending. It only asks the supplied function about each adjacent pair.

CallInjected policyMeaning
isSorted(xs, (a, b) => a <= b)left is no largerascending
isSorted(xs, (a, b) => a >= b)left is no smallerdescending
isSorted(labels, (a, b) => a.length <= b.length)shorter comes firstgeneric policy

Follow the types

Polymorphism replaces a concrete type with a type variable such as A. The implementation must work for every A, so the type signature removes irrelevant choices.

def partial1[A, B, C](a: A, f: (A, B) => C): B => C =
  b => f(a, b)

Read the signature from the outside in:

  1. We already have an A called a.
  2. We have a function that needs an A and a B to produce a C.
  3. We must return a function that needs a B and produces a C.

There is only one useful way to obtain that C: wait for b, then call f(a, b). This is the book's “follow the types” skill: the signature acts like a constraint puzzle.

Composition is a pipeline

A second useful shape returns a function by joining two functions:

def compose[A, B, C](f: B => C, g: A => B): A => C =
  a => f(g(a))

g runs first because its output has type B, exactly what f needs. For Flow Insight, that lets us describe a small pipeline without hard-coding every call site:

val liters: Reading => Double = reading => reading.liters
val roundOne: Double => Double = x => math.round(x * 10) / 10.0
val displayLiters: Reading => Double = compose(roundOne, liters)
Rust bridge

This is the same design instinct as passing a Rust closure into a generic function: keep the traversal or pipeline stable, and inject the policy. Scala's arrow types make the contract visible in the function signature.

Interactive coding checkpoint

Change the values or the ordering function. The loop is identical in both runs; only the injected comparator changes.

Browser lab / injected policy

Run isSorted

Result Sorted

The comparator accepted every neighboring pair.

ordered(7.5, 9.0) → true
ordered(9.0, 12.0) → true

Try this sequence:

  1. Run the default values with ascending order.
  2. Switch to descending. The same values should now fail.
  3. Enter 12, 10, 10, 4 and run descending. Equal neighbors are allowed.
  4. Enter 7, 11, 8 and inspect the first failing pair.

Quick checks

What changes when the same isSorted loop checks descending values?

For compose(f, g), which function runs first?

Spaced review

Before the next lesson, answer these without looking back:

Keep the compact version nearby: Higher-order functions reference.

Primary source: Chiusano and Bjarnason, Functional Programming in Scala, Chapter 2. The official first-edition exercise repository provides the chapter exercise stubs and answers. This page is an original, self-contained lesson; the book PDF is not required to run the checkpoint.

Ask me a follow-up question whenever a type signature feels like a puzzle. That is exactly the skill we are practicing.