Scala 中按名称调用与按值调用,需要澄清

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/13337338/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-22 04:42:40  来源:igfitidea点击:

Call by name vs call by value in Scala, clarification needed

scala

提问by James Raitsev

As I understand it, in Scala, a function may be called either

据我了解,在 Scala 中,可以调用一个函数

  • by-value or
  • by-name
  • 按值或
  • 按名字

For example, given the following declarations, do we know how the function will be called?

例如,给定以下声明,我们是否知道该函数将如何被调用?

Declaration:

宣言:

def  f (x:Int, y:Int) = x;

Call

称呼

f (1,2)
f (23+55,5)
f (12+3, 44*11)

What are the rules please?

请问规则是什么?

回答by dhg

The example you have given only uses call-by-value, so I will give a new, simpler, example that shows the difference.

您给出的示例仅使用按值调用,因此我将给出一个新的、更简单的示例来显示差异。

First, let's assume we have a function with a side-effect. This function prints something out and then returns an Int.

首先,让我们假设我们有一个有副作用的函数。这个函数打印出一些东西,然后返回一个Int.

def something() = {
  println("calling something")
  1 // return value
}

Now we are going to define two function that accept Intarguments that are exactly the same except that one takes the argument in a call-by-value style (x: Int) and the other in a call-by-name style (x: => Int).

现在我们将定义两个函数,它们接受Int完全相同的参数,除了一个采用按值调用样式 ( x: Int)的参数,另一个采用按名称调用样式 ( x: => Int) 的参数。

def callByValue(x: Int) = {
  println("x1=" + x)
  println("x2=" + x)
}

def callByName(x: => Int) = {
  println("x1=" + x)
  println("x2=" + x)
}

Now what happens when we call them with our side-effecting function?

现在当我们用我们的副作用函数调用它们时会发生什么?

scala> callByValue(something())
calling something
x1=1
x2=1

scala> callByName(something())
calling something
x1=1
calling something
x2=1

So you can see that in the call-by-value version, the side-effect of the passed-in function call (something()) only happened once. However, in the call-by-name version, the side-effect happened twice.

所以你可以看到,在 call-by-value 版本中,传入函数 call ( something())的副作用只发生了一次。但是,在按名称调用的版本中,副作用发生了两次。

This is because call-by-value functions compute the passed-in expression's value before calling the function, thus the samevalue is accessed every time. Instead, call-by-name functions recomputethe passed-in expression's value every time it is accessed.

这是因为按值调用函数在调用函数之前计算传入表达式的值,因此每次访问相同的值。相反,每次访问时,按名称调用函数都会重新计算传入表达式的值。

回答by Behrooz Tabesh

Here is an example from Martin Odersky:

这是 Martin Odersky 的一个例子:

def test (x:Int, y: Int)= x*x

We want to examine the evaluation strategy and determine which one is faster (less steps) in these conditions:

我们想检查评估策略并确定在这些条件下哪个更快(更少的步骤):

test (2,3)

call by value: test(2,3) -> 2*2 -> 4
call by name: test(2,3) -> 2*2 -> 4
Here the result is reached with the same number of steps.

call by value: test(2,3) -> 2*2 -> 4
call by name: test(2,3) -> 2*2 -> 4
这里的结果是用相同的步骤数达到的。

test (3+4,8)

call by value: test (7,8) -> 7*7 -> 49
call by name: (3+4) (3+4) -> 7(3+4)-> 7*7 ->49
Here call by value is faster.

call by value: test (7,8) -> 7*7 -> 49
call by name: (3+4) (3+4) -> 7(3+4)-> 7*7 ->49
Here call按值更快。

test (7,2*4)

call by value: test(7,8) -> 7*7 -> 49
call by name: 7 * 7 -> 49
Here call by name is faster

call by value: test(7,8) -> 7*7 -> 49
call by name: 7 * 7 -> 49
这里 call by name 更快

test (3+4, 2*4) 

call by value: test(7,2*4) -> test(7, 8) -> 7*7 -> 49
call by name: (3+4)(3+4) -> 7(3+4) -> 7*7 -> 49
The result is reached within the same steps.

按值调用:test(7,2*4) -> test(7, 8) -> 7*7 -> 49
按名称调用:(3+4) (3+4) -> 7(3+4) -> 7*7 -> 49
在相同的步骤中达到结果。

回答by resilva87

In the case of your example all the parameters will be evaluated beforeit's called in the function , as you're only defining them by value. If you want to define your parameters by nameyou should pass a code block:

在您的示例的情况下,所有参数都将在函数中调用之前进行评估,因为您只是按 value定义它们。如果要按名称定义参数则应传递代码块:

def f(x: => Int, y:Int) = x

This way the parameter xwill not be evaluated untilit's called in the function.

这样参数在函数中被调用之前x不会被评估。

This little posthere explains this nicely too.

这里的这篇小帖子也很好地解释了这一点

回答by user1496984

To iteratate @Ben's point in the above comments, I think it's best to think of "call-by-name" as just syntactic sugar. The parser just wraps the expressions in anonymous functions, so that they can be called at a later point, when they are used.

为了重复@Ben 在上述评论中的观点,我认为最好将“按名称调用”视为语法糖。解析器只是将表达式包装在匿名函数中,以便在以后使用它们时可以调用它们。

In effect, instead of defining

实际上,而不是定义

def callByName(x: => Int) = {
  println("x1=" + x)
  println("x2=" + x)
}

and running:

并运行:

scala> callByName(something())
calling something
x1=1
calling something
x2=1

You could also write:

你也可以这样写:

def callAlsoByName(x: () => Int) = {
  println("x1=" + x())
  println("x2=" + x())
}

And run it as follows for the same effect:

并按如下方式运行它以获得相同的效果:

callAlsoByName(() => {something()})

calling something
x1=1
calling something
x2=1

回答by guykaplan

I will try to explain by a simple use case rather than by just providing an example

我将尝试通过一个简单的用例来解释,而不是仅仅提供一个例子

Imagine you want to build a "nagger app"that will Nag you every time since time last you got nagged.

想象一下,您想构建一个“唠叨应用程序”,自从您上次被唠叨以来,每次都会唠叨您。

Examine the following implementations:

检查以下实现:

object main  {

    def main(args: Array[String]) {

        def onTime(time: Long) {
            while(time != time) println("Time to Nag!")
            println("no nags for you!")
        }

        def onRealtime(time: => Long) {
            while(time != time) println("Realtime Nagging executed!")
        }

        onTime(System.nanoTime())
        onRealtime(System.nanoTime())
    }
}

In the above implementation the nagger will work only when passing by name the reason is that, when passing by value it will re-used and therefore the value will not be re-evaluated while when passing by name the value will be re-evaluated every time the variables is accessed

在上面的实现中,nagger 只会在按名称传递时工作,原因是,当按值传递时,它将被重新使用,因此不会重新评估该值,而在按名称传递时,值将被重新评估访问变量的时间

回答by sofiene zaghdoudi

Typically, parameters to functions are by-value parameters; that is, the value of the parameter is determined before it is passed to the function. But what if we need to write a function that accepts as a parameter an expression that we don't want evaluated until it's called within our function? For this circumstance, Scala offers call-by-name parameters.

通常,函数的参数是按值参数;也就是说,参数的值在传递给函数之前就已经确定了。但是,如果我们需要编写一个函数,该函数接受一个表达式作为参数,而在我们的函数中调用该表达式之前我们不想对其求值,该怎么办?对于这种情况,Scala 提供了按名称调用的参数。

A call-by-name mechanism passes a code block to the callee and each time the callee accesses the parameter, the code block is executed and the value is calculated.

按名称调用机制将代码块传递给被调用者,每次被调用者访问参数时,都会执行代码块并计算值。

object Test {
def main(args: Array[String]) {
    delayed(time());
}

def time() = {
  println("Getting time in nano seconds")
  System.nanoTime
}
def delayed( t: => Long ) = {
  println("In delayed method")
  println("Param: " + t)
  t
}
}
 1. C:/>scalac Test.scala 
 2. scala Test
 3. In delayed method
 4. Getting time in nano seconds
 5. Param: 81303808765843
 6. Getting time in nano seconds

回答by Harmeet Singh Taara

As i assume, the call-by-valuefunction as discuss above pass just the values to the function. According to Martin OderskyIt is a Evaluation strategy follow by a Scala that play the important role in function evaluation. But, Make it simple to call-by-name. its like a pass the function as a argument to the method also know as Higher-Order-Functions. When the method access the value of passed parameter, it call the implementation of passed functions. as Below:

正如我所假设的,call-by-value上面讨论的函数只将值传递给函数。根据Martin OderskyIt 是 Scala 遵循的评估策略,在函数评估中起着重要作用。但是,让它变得简单call-by-name。它就像将函数作为参数传递给方法也称为Higher-Order-Functions. 当方法访问传递参数的值时,它调用传递函数的实现。如下:

According to @dhg example, create the method first as:

根据@dhg 示例,首先创建方法为:

def something() = {
 println("calling something")
 1 // return value
}  

This function contain one printlnstatement and return an integer value. Create the function, who have arguments as a call-by-name:

此函数包含一个println语句并返回一个整数值。创建函数,其参数为call-by-name

def callByName(x: => Int) = {
 println("x1=" + x)
 println("x2=" + x)
}

This function parameter, is define an anonymous function who have return one integer value. In this xcontain an definition of function who have 0passed arguments but return intvalue and our somethingfunction contain same signature. When we call the function, we pass the function as a argument to callByName. But in the case of call-by-valueits only pass the integer value to the function. We call the function as below:

这个函数参数,是定义一个匿名函数,返回一个整数值。其中x包含一个0传递参数但返回int值的函数的定义,我们的something函数包含相同的签名。当我们调用函数时,我们将函数作为参数传递给callByName。但是在call-by-value它只将整数值传递给函数的情况下。我们调用函数如下:

scala> callByName(something())
 calling something
 x1=1
 calling something
 x2=1 

In this our somethingmethod called twice, because when we access the value of xin callByNamemethod, its call to the defintion of somethingmethod.

在此我们的something方法调用两次,因为当我们访问的值xcallByName方法,它调用的把定义something方法。

回答by Ram Ghadiyaram

Call by value is general use case as explained by many answers here..

正如此处的许多答案所解释的那样,按值调用是一般用例。

Call-by-namepasses a code block to the caller and each time the caller accesses the parameter, the code block is executed and the value is calculated.

Call-by-name将一个代码块传递给调用者,每次调用者访问参数时,都会执行该代码块并计算其值。

I will try to demonstrate call by name more simple way with use cases below

我将尝试通过下面的用例以更简单的方式演示按名称调用

Example 1:

示例 1:

Simple example/use case of call by name is below function, which takes function as parameter and gives the time elapsed.

按名称调用的简单示例/用例在函数下方,它以函数为参数并给出经过的时间。

 /**
   * Executes some code block and prints to stdout the 
time taken to execute   the block 
for interactive testing and debugging.
   */
  def time[T](f: => T): T = {
    val start = System.nanoTime()
    val ret = f
    val end = System.nanoTime()

    println(s"Time taken: ${(end - start) / 1000 / 1000} ms")

    ret
  }

Example 2:

示例 2:

apache spark (with scala) uses logging using call by name way see Loggingtraitin which its lazily evaluateswhether log.isInfoEnabledor not from the below method.

apache spark(使用scala)使用按名称调用的日志记录方式查看Logging特性,其中它懒惰地评估是否log.isInfoEnabled来自以下方法。

protected def logInfo(msg: => String) {
     if (log.isInfoEnabled) log.info(msg)
 }

回答by Nijanthan Vijayakumar

In a Call by Value, the value of the expression is pre-computed at the time of the function call and that particular value is passed as the parameter to the corresponding function. The same value will be used all throughout the function.

Call by Value 中,表达式的值是在函数调用时预先计算的,并且该特定值作为参数传递给相应的函数。在整个函数中都将使用相同的值。

Whereas in a Call by Name, the expression itself is passed as a parameter to the function and it is only computed inside the function, whenever that particular parameter is called.

而在Call by Name 中,表达式本身作为参数传递给函数,并且仅在调用该特定参数时在函数内部计算。

The difference between Call by Name and Call by Value in Scala could be better understood with the below example:

通过以下示例可以更好地理解 Scala 中按名称调用和按值调用之间的区别:

Code Snippet

代码片段

object CallbyExample extends App {

  // function definition of call by value
  def CallbyValue(x: Long): Unit = {
    println("The current system time via CBV: " + x);
    println("The current system time via CBV " + x);
  }

  // function definition of call by name
  def CallbyName(x: => Long): Unit = {
    println("The current system time via CBN: " + x);
    println("The current system time via CBN: " + x);
  }

  // function call
  CallbyValue(System.nanoTime());
  println("\n")
  CallbyName(System.nanoTime());
}

Output

输出

The current system time via CBV: 1153969332591521
The current system time via CBV 1153969332591521


The current system time via CBN: 1153969336749571
The current system time via CBN: 1153969336856589

In the above code snippet, for the function call CallbyValue(System.nanoTime()), the system nano time is pre-calculated and that pre-calculated value has been passed a parameter to the function call.

在上面的代码片段中,对于函数调用CallbyValue(System.nanoTime()),系统纳米时间是预先计算的,并且该预先计算的值已传递给函数调用的参数。

But in the CallbyName(System.nanoTime())function call, the expression "System.nanoTime())" itself is passed as a parameter to the function call and the value of that expression is calculated when that parameter is used inside the function.

但是在CallbyName(System.nanoTime())函数调用中,表达式“System.nanoTime())”本身作为参数传递给函数调用,并在函数内部使用该参数时计算该表达式的值.

Notice the function definition of the CallbyName function, where there is a =>symbol separating the parameter xand its datatype. That particular symbol there indicates the function is of call by name type.

请注意 CallbyName 函数的函数定义,其中有一个=>符号分隔参数x及其数据类型。那里的特定符号表示该函数是按名称调用的类型。

In other words, the call by value function arguments are evaluated once before entering the function, but the call by name function arguments are evaluated inside the function only when they are needed.

换句话说,按值调用函数参数在进入函数之前被评估一次,但按名称调用函数参数仅在需要时才在函数内部评估。

Hope this helps!

希望这可以帮助!

回答by Madpoptart

Here is a quick example I coded to help a colleague of mine who is currently taking the Scala course. What I thought was interesting is that Martin didn't use the && question answer presented earlier in the lecture as an example. In any event I hope this helps.

这是我编写的一个快速示例,用于帮助我目前正在参加 Scala 课程的同事。我认为有趣的是,Martin 没有使用讲座前面提到的 && 问题答案作为示例。无论如何,我希望这会有所帮助。

val start = Instant.now().toEpochMilli

val calc = (x: Boolean) => {
    Thread.sleep(3000)
    x
}


def callByValue(x: Boolean, y: Boolean): Boolean = {
    if (!x) x else y
}

def callByName(x: Boolean, y: => Boolean): Boolean = {
    if (!x) x else y
}

new Thread(() => {
    println("========================")
    println("Call by Value " + callByValue(false, calc(true)))
    println("Time " + (Instant.now().toEpochMilli - start) + "ms")
    println("========================")
}).start()


new Thread(() => {
    println("========================")
    println("Call by Name " + callByName(false, calc(true)))
    println("Time " + (Instant.now().toEpochMilli - start) + "ms")
    println("========================")
}).start()


Thread.sleep(5000)

The output of the code will be the following:

代码的输出如下:

========================
Call by Name false
Time 64ms
========================
Call by Value false
Time 3068ms
========================