Scala - By-Name Parameters

• By-name parameters in Scala delay evaluation until the parameter is actually used, enabling lazy evaluation patterns and control structure abstractions without macros or special compiler support.

Key Insights

• By-name parameters in Scala delay evaluation until the parameter is actually used, enabling lazy evaluation patterns and control structure abstractions without macros or special compiler support. • The syntax => Type creates a by-name parameter that wraps expressions in zero-argument functions, re-evaluating the expression each time the parameter is accessed within the method body. • By-name parameters are essential for building DSLs, custom control structures, and performance optimizations where you need conditional evaluation or want to avoid computing expensive values that might not be used.

Understanding By-Name Parameters

By-name parameters represent one of Scala’s most powerful yet underutilized features. Unlike regular parameters that evaluate before the method call (call-by-value), by-name parameters delay evaluation until the point of use within the method body.

The syntax is straightforward: prefix the parameter type with =>:

def byValue(x: Int): Unit = {
  println("Method called")
  println(s"Value: $x")
}

def byName(x: => Int): Unit = {
  println("Method called")
  println(s"Value: $x")
}

// Call-by-value: expression evaluates first
byValue({ println("Evaluating..."); 42 })
// Output:
// Evaluating...
// Method called
// Value: 42

// Call-by-name: expression evaluates when used
byName({ println("Evaluating..."); 42 })
// Output:
// Method called
// Evaluating...
// Value: 42

The difference becomes critical when dealing with side effects, expensive computations, or expressions that might throw exceptions.

Multiple Evaluations and Performance Implications

A crucial characteristic of by-name parameters: they re-evaluate every time you access them. This isn’t lazy evaluation—it’s deferred evaluation.

def demonstrateMultipleEvaluation(x: => Int): Unit = {
  println(s"First access: $x")
  println(s"Second access: $x")
  println(s"Third access: $x")
}

var counter = 0
demonstrateMultipleEvaluation({
  counter += 1
  println(s"Evaluating (counter = $counter)")
  counter * 10
})

// Output:
// Evaluating (counter = 1)
// First access: 10
// Evaluating (counter = 2)
// Second access: 20
// Evaluating (counter = 3)
// Third access: 30

To achieve true lazy evaluation with single computation, combine by-name parameters with lazy val:

def lazyEvaluation(x: => Int): Unit = {
  lazy val cached = x
  println(s"First access: $cached")
  println(s"Second access: $cached")
  println(s"Third access: $cached")
}

counter = 0
lazyEvaluation({
  counter += 1
  println(s"Evaluating (counter = $counter)")
  counter * 10
})

// Output:
// Evaluating (counter = 1)
// First access: 10
// Second access: 10
// Third access: 10

Building Custom Control Structures

By-name parameters enable you to create control structures that look and behave like built-in language features:

def unless(condition: => Boolean)(block: => Unit): Unit = {
  if (!condition) block
}

def repeat(times: Int)(block: => Unit): Unit = {
  var i = 0
  while (i < times) {
    block
    i += 1
  }
}

def whileLoop(condition: => Boolean)(body: => Unit): Unit = {
  if (condition) {
    body
    whileLoop(condition)(body)
  }
}

// Usage
var x = 0
unless(x > 5) {
  println("x is not greater than 5")
}

repeat(3) {
  println("Repeated action")
}

var count = 0
whileLoop(count < 3) {
  println(s"Count: $count")
  count += 1
}

These constructs integrate seamlessly with Scala’s syntax, making your DSLs more readable and maintainable.

Conditional Execution and Short-Circuiting

By-name parameters excel at implementing conditional logic where you want to avoid evaluating expensive operations unless necessary:

def expensiveComputation(): Int = {
  println("Running expensive computation...")
  Thread.sleep(1000)
  42
}

def logIfDebug(debugEnabled: => Boolean, message: => String): Unit = {
  if (debugEnabled) {
    println(s"[DEBUG] $message")
  }
}

// Regular parameter version (always evaluates)
def logIfDebugEager(debugEnabled: Boolean, message: String): Unit = {
  if (debugEnabled) {
    println(s"[DEBUG] $message")
  }
}

val isDebug = false

// With by-name: expensive computation never runs
logIfDebug(isDebug, s"Result: ${expensiveComputation()}")

// With eager evaluation: computation runs even when not needed
// logIfDebugEager(isDebug, s"Result: ${expensiveComputation()}")

This pattern is particularly valuable in logging frameworks, assertion libraries, and configuration-dependent code paths.

Implementing Try-Catch Abstractions

By-name parameters allow you to wrap exception-prone code elegantly:

def attempt[T](block: => T): Either[Throwable, T] = {
  try {
    Right(block)
  } catch {
    case e: Throwable => Left(e)
  }
}

def withRetry[T](maxAttempts: Int)(block: => T): T = {
  var attempts = 0
  var lastException: Throwable = null
  
  while (attempts < maxAttempts) {
    try {
      return block
    } catch {
      case e: Throwable =>
        lastException = e
        attempts += 1
        if (attempts < maxAttempts) {
          Thread.sleep(100 * attempts)
        }
    }
  }
  throw lastException
}

// Usage
val result = attempt {
  val data = """{"value": 42}"""
  // Parse JSON or perform risky operation
  42
}

val retriedResult = withRetry(3) {
  // Simulated flaky operation
  if (scala.util.Random.nextBoolean()) throw new Exception("Temporary failure")
  "Success"
}

Resource Management Patterns

Combine by-name parameters with higher-order functions for resource management:

import scala.io.Source
import java.io.Closeable

def using[T <: Closeable, R](resource: => T)(block: T => R): R = {
  var res: T = null.asInstanceOf[T]
  try {
    res = resource
    block(res)
  } finally {
    if (res != null) res.close()
  }
}

def measureTime[T](block: => T): (T, Long) = {
  val start = System.nanoTime()
  val result = block
  val end = System.nanoTime()
  (result, end - start)
}

// Usage
val lines = using(Source.fromFile("data.txt")) { source =>
  source.getLines().toList
}

val (result, timeNanos) = measureTime {
  (1 to 1000000).sum
}
println(s"Computation took ${timeNanos / 1000000}ms")

Boolean Operators and Logical Expressions

Implement custom boolean operators that short-circuit properly:

class LazyBoolean(value: => Boolean) {
  def &&(other: => Boolean): Boolean = {
    if (!value) false
    else other
  }
  
  def ||(other: => Boolean): Boolean = {
    if (value) true
    else other
  }
}

implicit def booleanToLazy(b: => Boolean): LazyBoolean = new LazyBoolean(b)

def expensiveCheck1(): Boolean = {
  println("Checking condition 1...")
  false
}

def expensiveCheck2(): Boolean = {
  println("Checking condition 2...")
  true
}

// Only first check executes due to short-circuiting
if (expensiveCheck1() && expensiveCheck2()) {
  println("Both true")
}

Practical Guidelines

Use by-name parameters when:

  1. The parameter might not be used: Conditional logging, assertions, or optional computations
  2. You need multiple evaluations: Control structures that repeat operations
  3. Building DSLs: Creating fluent, readable APIs that mimic language constructs
  4. Performance matters: Avoiding expensive computations in hot paths

Avoid by-name parameters when:

  1. The parameter is always used once: No benefit over regular parameters
  2. Side effects are problematic: Multiple evaluations could cause issues
  3. Debugging is critical: Stack traces and evaluation order become less obvious

By-name parameters provide the foundation for many of Scala’s elegant abstractions. Master them to write more expressive, efficient, and maintainable code that leverages Scala’s functional programming capabilities without sacrificing readability or performance.

Liked this? There's more.

Every week: one practical technique, explained simply, with code you can use immediately.