Landa Function

Understanding Scala call-by-name

• scala

Scala call by name is a powerful feature missed by many Scala developers. By using a common use case like logging we will see what call by name gives us and what are the alternatives. When we call the logger, we usually specify a log level and a message:

class Logger { 
..
def log(logLevel: Int, message: String): Unit = .. 
} 

Known best practice is to use the appropriate logging level according to the need. However, logging levels doesn’t prevent from us from computing the message for our log line. From my experience, many debug messages require heavy computation which is not part of the main business logic. If the log level is higher than debug, these debug messages should not be computed. A common pattern that I saw at some companies was as following:

def log(logLevel: Int, message: String): Unit = {
	if logLevelEnabled(logLevel) { .. actualLogging ..}
}

// in another file
val message = someHeavyComputation() // call to an external service or a Spark action
log(1, message)

The log function implementation assures that messages from lower log level than specificized will not be logged, but the message is going to be computed anyway. Another solution will be to utilize Scala high-order functions and pass the message a function of () => String :

def log(logLevel: Int, messageFunc: () => String): Unit = {
	if logLevelEnabled(logLevel) { 
		val message = messageFunc()
		actualLogging(message) 
		..

}

Even though we have a solution, the syntax doesn’t feel right. In functional programing we assume referential transparency, making a function without arguments equal to a statement. This is exactly what the call by name feature provides using the syntax:
def func(statement: => Type).

Lets look at our log function with using a call by name parameter :

def log(logLevel: Int, message: => String): Unit = {
	if logLevelEnabled(logLevel) { 
		actualLogging(message) 
		..
	}
}

Using the syntax above the message statement will be evaluated every time it is called, just like a function with a single parameter.

A final example to emphasize the power of call by name can be found in the code below. In this snippet the log function will be called 10 times and the “foo” will be printed 10 times as well.

def logMultiple(nTimes: Int, message: => String): Unit = {
	for (_ <- 1 to nTimes) log(message)
}
logMultiple(10, {
	println("foo")
	"test"
})

To summarize, if I want to pass an expression instead of a value I would use a call by name parameter. Every access to the call by name parameter will evaluate the expression. For real world example look at Twitters Logger implementation

comments powered by Disqus