Skip to content
AgileLogics
5 min read

Higher-Order Functions

Prerequisites: Functions as values, Generics

A higher-order function takes a function as a parameter or returns one. They let you separate what to do from how to do it.

Taking functions

def retry[A](attempts: Int)(action: => A): A =
  try action
  catch
    case e: Exception if attempts > 1 =>
      println(s"failed: ${e.getMessage}, retrying")
      retry(attempts - 1)(action)

@main def retrying(): Unit =
  var calls = 0
  val result = retry(3) {
    calls += 1
    if calls < 3 then throw RuntimeException(s"call $calls") else "ok"
  }
  println(result)
failed: call 1, retrying
failed: call 2, retrying
ok
  • action: => A is a by-name parameter: the block isn't evaluated when retry is called, but each time action is used inside it.
  • Two parameter lists — (attempts: Int)(action: => A) — let callers pass the block in braces, so retry looks like a built-in control structure.

Returning functions

def multiplier(factor: Int): Int => Int = x => x * factor

def discount(percent: Double): Double => Double =
  price => price * (1 - percent / 100)

@main def returning(): Unit =
  val triple = multiplier(3)
  println(triple(7))
  val sale = discount(25)
  println(List(100.0, 40.0).map(sale))
21
List(75.0, 30.0)

The returned function captures factor — it's a closure.

Composing functions

@main def composing(): Unit =
  val trim: String => String = _.trim
  val lower: String => String = _.toLowerCase
  val slug: String => String = _.replaceAll("[^a-z0-9]+", "-")

  val toSlug = trim andThen lower andThen slug
  println(toSlug("  Hello Scala World  "))
hello-scala-world

f andThen g runs f, then g on its result (f compose g is the reverse).

Currying and partial application

def log(level: String)(message: String): String = s"[$level] $message"

@main def currying(): Unit =
  val warn = log("WARN")
  println(warn("disk almost full"))
  println(List("a", "b").map(log("INFO")))
[WARN] disk almost full
List([INFO] a, [INFO] b)

A method with several parameter lists can be called with just the first, producing a function that waits for the rest.