Lesson 6 / 25

Functions as Values

Write lambdas, higher-order functions, currying and by-name parameters.

Functions are first-class

Functions are values with types such as Int => Int or (String, Int) => Boolean. Write lambdas as (x: Int) => x * 2 or, when the type is known, x => x * 2, and use the placeholder _ for simple cases: _ * 2, _ + _. Higher-order functions take or return functions, like map, filter and your own retry(times)(action). Methods (def) are automatically converted to function values when needed (eta-expansion), so orders.map(formatOrder) works. Multiple parameter lists (def fold[B](z: B)(op: (B, A) => B)) enable currying, better type inference and nice syntax for passing a block as the last argument. By-name parameters (action: => A) delay evaluating an argument until it is used, which lets you write control structures such as retry, timed or when. Functions compose with andThen and compose. Closures capture variables from their surrounding scope. Prefer pure functions (same input, same output, no side effects): they are easy to test and to run in parallel.

Higher-order functions, currying and by-name parameters

Building a small retry helper and composing functions.

import scala.util.{Try, Success, Failure}

def retry[A](times: Int)(action: => A): Try[A] =       // action is by-name: re-evaluated each attempt
  Try(action) match
    case Failure(_) if times > 1 => retry(times - 1)(action)
    case result                  => result

var calls = 0
val result = retry(3) {
  calls += 1
  if calls < 3 then throw RuntimeException("gateway busy") else "paid"
}
println(result)                                          // Success(paid)

val addGst: BigDecimal => BigDecimal = _ * BigDecimal("1.18")
val roundRupees: BigDecimal => BigDecimal = _.setScale(2, BigDecimal.RoundingMode.HALF_UP)
val finalPrice = addGst andThen roundRupees              // function composition
println(finalPrice(BigDecimal("99.99")))                 // 117.99

def discount(percent: Int)(amount: BigDecimal): BigDecimal = amount * (100 - percent) / 100
val festive = discount(10)                              // partially applied: BigDecimal => BigDecimal
println(List(BigDecimal(100), BigDecimal(250)).map(festive))   // List(90, 225)

Handing over instructions, not results

A by-name parameter is like handing someone a recipe instead of a cooked dish: they can cook it when they need it, and again if the first attempt burns.

Quick check: What does a by-name parameter `action: => A` do?

  • Delays evaluating the argument until it is used, possibly several times
  • Evaluates the argument once before the call
  • Makes the parameter optional
  • Passes the argument by reference like C++
Answer

Delays evaluating the argument until it is used, possibly several times — By-name arguments are evaluated each time they are referenced inside the method.