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: => Ais a by-name parameter: the block isn't evaluated whenretryis called, but each timeactionis used inside it.- Two parameter lists —
(attempts: Int)(action: => A)— let callers pass the block in braces, soretrylooks 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.