简体   繁体   中英

When to use call-by-name and call-by-value?

I understand the basic concept of call-by-name and call-by-value, and I have also looked into handful number of examples. However, I am not very clear about when to use call-by-name. What would be a real-world scenario where call-by-name would have a significant advantage or performance gain over the other call type? What should be the correct thinking approach to select a call type while designing a method?

There are plenty of places were call-by-name may gain performance or even correctness.

Simple performance example: logging. Imagine an interface like this:

trait Logger {
  def info(msg: => String)
  def warn(msg: => String)
  def error(msg: => String)
}

And then used like this:

logger.info("Time spent on X: " + computeTimeSpent)

If the info method doesn't do anything (because, say, the logging level was configured for higher than that), then computeTimeSpent never gets called, saving time. This happens a lot with loggers, where one often sees string manipulation which can be expensive relative to the tasks being logged.

Correctness example: logic operators.

You have probably seen code like this:

if (ref != null && ref.isSomething)

Say you declared && method like this:

trait Boolean {
  def &&(other: Boolean): Boolean
}

then, whenever ref is null , you'll get an error because isSomething will be called on a null reference before being passed to && . For this reason, the actual declaration is:

trait Boolean {
  def &&(other: => Boolean): Boolean =
    if (this) other else this
}

So one may actually wonder is when to use call-by-value. In fact, in the Haskell programming language everything works similar to how call-by-name works (similar, but not the same).

There are good reasons not to use call-by-name: it is slower, it creates more classes (meaning the program takes longer to load), it consumes more memory, and it is different enough that many have difficult reasoning about it.

Call by name means the value is evaluated at the time it is accessed, while with call by value the value is evaluated first and then passed to the method.

To see the difference, consider this example (completely non-functional programming with only side effects ;) ). Say you want to create a function which measures how much time some operation takes. You can do it with call-by-name:

def measure(action: => Unit) = {
    println("Starting to measure time")
    val startTime = System.nanoTime
    action
    val endTime = System.nanoTime
    println("Operation took "+(endTime-startTime)+" ns")
}

measure {
    println("Will now sleep a little")
    Thread.sleep(1000)
}

You will get the result (YMMV):

Starting to measure time
Will now sleep a little
Operation took 1000167919 ns

But if you change only the signature of measure to measure(action: Unit) so it uses pass-by-value, the result will be:

Will now sleep a little
Starting to measure time
Operation took 1760 ns

As you can see, the action is evaluated before measure even starts and also the elapsed time is close to 0 due to the action already having been run before the method was called.

Here, pass-by-name allows the expected behavior of the method to be achieved. In some cases it doesn't influence the correctness, but does influence the performance, for example in logging frameworks where a complex expression might not need to be evaluated at all if the result is not used.

The simple way it might be explained is

call-by-value functions compute the passed-in expression's value before calling the function, thus the same value is accessed every time. However, call-by-name functions recompute the passed-in expression's value every time it is accessed.

I've always thought this terminology is needlessly confusing. A function can have multiple parameters which vary in their call-by-name vs call-by-value status. So it's not that a function is call-by-name or call-by-value, it's that each of its parameters may be pass-by-name or pass-by-value. Furthermore, "call-by-name" has nothing to do with names. => Int is a different type from Int; it's "function of no arguments that will generate an Int" vs just Int. Once you've got first-class functions you don't need to invent call-by-name terminology to describe this.

When a call-by-name parameter is used more than once in a function, the parameter is evaluated more than once.

As the parameter being passed in should be a pure function call per Functional Programming, each evaluation within the called function will always generate the same result. Therefore, call-by-name would be more wasteful than the conventional call-by-value.

对于按名称调用的日志记录用例来说很好: https ://www.tutorialspoint.com/scala/functions_call_by_name.htm#:~:text=For%20this%20circumstance%2C%20Scala%20offers,and%20the% 20value%20is%20calculated

Call by name gets evaluated each time it is called. But is never evaluated if it is never called. So if your use case is ok with, or needs, new evaluation each time (eg getting fresh/latest data from somewhere) it is useful, and it is never evaluated if never used. See code below. Remove the "=>" and see what happens. There will be only one call before the method "m" is called.

object RunMe extends App {

  def m(b: Boolean, m: => String) = {
    if(b) m
  }

  def f = {
    println("hello world")
    "completed"
  }
  println("starting...")
  m(false, f)

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM