250

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

  • 按价值或
  • 按名字

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

宣言:

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

称呼

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

请问有什么规则吗?

4

17 回答 17

569

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

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

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

现在我们将定义两个函数,它们接受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)
}

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

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

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

所以你可以看到,在按值调用的版本中,传入的函数调用 ( ) 的副作用something()只发生了一次。然而,在点名版本中,副作用发生了两次。

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

于 2012-11-12T01:40:20.580 回答
53

下面是 Martin Odersky 的一个例子:

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

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

test (2,3)

按值调用:test(2,3) -> 2*2 -> 4
按名称调用:test(2,3) -> 2*2 -> 4
这里以相同的步数达到结果。

test (3+4,8)

按值调用:test (7,8) -> 7*7 -> 49
按名称调用:(3+4) (3+4) -> 7 (3+4)-> 7*7 ->49
这里调用按价值计算更快。

test (7,2*4)

按值调用:test(7,8) -> 7*7 -> 49
按名称调用:7 * 7 -> 49
这里按名称调用更快

test (3+4, 2*4) 

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

于 2013-07-27T19:27:19.727 回答
16

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

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

这样,在函数中调用参数之前x,不会对参数进行评估。

这里的这篇小文章也很好地解释了这一点。

于 2012-11-12T01:45:49.283 回答
10

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

实际上,而不是定义

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

并运行:

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

你也可以写:

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

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

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

calling something
x1=1
calling something
x2=1
于 2014-10-30T05:42:46.127 回答
6

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

想象一下,你想构建一个“nagger 应用程序”,它会在你上次被唠叨后每次唠叨你。

检查以下实现:

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())
    }
}

在上述实现中,nagger 仅在按名称传递时才起作用,原因是,当按值传递时,它将被重新使用,因此不会重新评估该值,而当按名称传递时,每个值都会重新评估访问变量的时间

于 2014-10-09T11:52:55.990 回答
4

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

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

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. 斯卡拉测试
 3.延迟法
 4.以纳秒为单位获取时间
 5. 参数:81303808765843
 6.以纳秒为单位获取时间
于 2016-02-25T11:04:52.557 回答
2

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

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

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

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

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

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

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

在这个我们的something方法调用了两次,因为当我们访问方法中的值时,它调用了方法的x定义。callByNamesomething

于 2015-04-19T09:15:53.843 回答
2

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

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

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

示例 1:

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

 /**
   * 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
  }

示例 2:

apache spark(带有scala)使用按名称调用的方式使用日志记录,请参阅Logging特征 ,其中它懒惰地评估是否log.isInfoEnabled从下面的方法。

protected def logInfo(msg: => String) {
     if (log.isInfoEnabled) log.info(msg)
 }
于 2017-07-30T19:24:52.157 回答
2

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

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

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

代码片段

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());
}

输出

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

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

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

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

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

希望这可以帮助!

于 2018-02-10T12:11:43.427 回答
2

这是我编写的一个快速示例,以帮助我目前正在学习 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)

代码的输出将如下所示:

========================
Call by Name false
Time 64ms
========================
Call by Value false
Time 3068ms
========================
于 2018-12-27T18:00:55.370 回答
1

参数通常是按值传递的,这意味着它们将在被替换到函数体之前被评估。

您可以在定义函数时使用双箭头强制按名称调用参数。

// first parameter will be call by value, second call by name, using `=>`
def returnOne(x: Int, y: => Int): Int = 1

// to demonstrate the benefits of call by name, create an infinite recursion
def loop(x: Int): Int = loop(x)

// will return one, since `loop(2)` is passed by name so no evaluated
returnOne(2, loop(2))

// will not terminate, since loop(2) will evaluate. 
returnOne(loop(2), 2) // -> returnOne(loop(2), 2) -> returnOne(loop(2), 2) -> ... 
于 2017-01-11T22:05:13.690 回答
1

互联网上已经有很多关于这个问题的奇妙答案。我将编写我收集的关于该主题的几个解释和示例的汇编,以防万一有人觉得它有帮助

介绍

按值调用 (CBV)

通常,函数的参数是按值调用的参数;也就是说,在评估函数本身之前,从左到右评估参数以确定它们的值

def first(a: Int, b: Int): Int = a
first(3 + 4, 5 + 6) // will be reduced to first(7, 5 + 6), then first(7, 11), and then 7

点名呼叫 (CBN)

但是,如果我们需要编写一个函数,该函数接受一个表达式作为参数,在我们的函数中调用它之前我们不会对其进行评估呢?对于这种情况,Scala 提供了按名称调用的参数。这意味着参数按原样传递给函数,并且在替换后进行评估

def first1(a: Int, b: => Int): Int = a
first1(3 + 4, 5 + 6) // will be reduced to (3 + 4) and then to 7

按名称调用机制将代码块传递给调用,每次调用访问参数时,都会执行代码块并计算值。在以下示例中,延迟打印一条消息,表明已输入该方法。接下来,延迟打印一条带有其值的消息。最后,延迟返回 't':

 object Demo {
       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)
       }
    }

在延迟方法
中以纳秒为单位获取时间
参数:2027245119786400

每种情况的优缺点

CBN: +更频繁地终止 * 检查以下终止上方 * + 如果相应的参数在函数体的评估中未使用,则不会评估函数参数 - 它更慢,它创建更多类(意味着程序需要加载时间更长)并且它消耗更多内存。

CBV: + 它通常比 CBN 效率高出指数级,因为它避免了这种按名称调用所需要的参数表达式的重复重新计算。它只对每个函数参数进行一次评估 + 它在命令式效果和副作用方面表现得更好,因为您往往会更好地了解何时评估表达式。- 它可能会在其参数评估期间导致循环 * 在终止上方检查以下 *

如果不能保证终止怎么办?

- 如果表达式 e 的 CBV 评估终止,则 e 的 CBN 评估也终止 - 另一个方向不正确

非终止示例

def first(x:Int, y:Int)=x

首先考虑表达式(1,循环)

CBN: first(1,loop) → 1 CBV: first(1,loop) → 减少这个表达式的参数。由于 one 是一个循环,因此它会无限地减少参数。它不会终止

每种情况下的行为差异

让我们定义一个方法测试,它将是

Def test(x:Int, y:Int) = x * x  //for call-by-value
Def test(x: => Int, y: => Int) = x * x  //for call-by-name

案例1测试(2,3)

test(2,3)   →  2*2 → 4

由于我们从已经评估的参数开始,因此按值调用和按名称调用的步骤数量相同

案例2测试(3+4,8)

call-by-value: test(3+4,8) → test(7,8) → 7 * 7 → 49
call-by-name: (3+4)*(3+4) → 7 * (3+4) → 7 * 7 → 49

在这种情况下,按值调用执行的步骤更少

Case3 测试(7, 2*4)

call-by-value: test(7, 2*4) → test(7,8) → 7 * 7 → 49
call-by-name: (7)*(7) → 49

我们避免对第二个参数进行不必要的计算

Case4 测试(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

不同的方法

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

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

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

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

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

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

所以你可以看到,在按值调用的版本中,传入的函数调用(something())的副作用只发生了一次。然而,在点名版本中,副作用发生了两次。

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

最好使用 CALL-BY-NAME 的示例

来自:https ://stackoverflow.com/a/19036068/1773841

简单的性能示例:日志记录。

让我们想象一个这样的界面:

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

然后像这样使用:

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

如果 info 方法不执行任何操作(例如,日志级别配置为高于此级别),则永远不会调用 computeTimeSpent,从而节省时间。这种情况在记录器中经常发生,人们经常看到字符串操作相对于正在记录的任务而言可能是昂贵的。

正确性示例:逻辑运算符。

你可能见过这样的代码:

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

想象一下,你会像这样声明 && 方法:

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

然后,每当 ref 为 null 时,您都会收到错误消息,因为 isSomething 将在传递给 && 之前在 null 引用上调用。因此,实际的声明是:

trait Boolean {
  def &&(other: => Boolean): Boolean =
    if (this) this else other
}
于 2017-07-13T06:05:48.230 回答
1

通过一个示例应该可以帮助您更好地理解差异。

让我们定义一个返回当前时间的简单函数:

def getTime = System.currentTimeMillis

现在我们将通过name定义一个函数,它打印两次延迟一秒:

def getTimeByName(f: => Long) = { println(f); Thread.sleep(1000); println(f)}

一个按价值计算

def getTimeByValue(f: Long) = { println(f); Thread.sleep(1000); println(f)}

现在让我们调用每个:

getTimeByName(getTime)
// prints:
// 1514451008323
// 1514451009325

getTimeByValue(getTime)
// prints:
// 1514451024846
// 1514451024846

结果应该解释差异。该片段可在此处获得。

于 2017-12-28T08:54:28.180 回答
0

CallByName使用时callByValue调用,遇到语句时调用。

例如:-

我有一个无限循环,即如果您执行此功能,我们将永远不会得到scala提示。

scala> def loop(x:Int) :Int = loop(x-1)
loop: (x: Int)Int

callByName函数将上述方法作为loop参数,并且从不在其体内使用。

scala> def callByName(x:Int,y: => Int)=x
callByName: (x: Int, y: => Int)Int

在执行callByName方法时,我们没有发现任何问题(我们得到scala提示),因为我们没有在函数内部使用循环callByName函数。

scala> callByName(1,loop(10))
res1: Int = 1
scala> 

一个callByValue函数将上述loop方法作为参数,因为函数或表达式的结果在执行外部函数之前通过loop递归执行的函数进行评估,我们永远不会得到scala提示。

scala> def callByValue(x:Int,y:Int) = x
callByValue: (x: Int, y: Int)Int

scala> callByValue(1,loop(1))
于 2015-09-11T06:42:03.177 回答
0

看到这个:

    object NameVsVal extends App {

  def mul(x: Int, y: => Int) : Int = {
    println("mul")
    x * y
  }
  def add(x: Int, y: Int): Int = {
    println("add")
    x + y
  }
  println(mul(3, add(2, 1)))
}

y: => Int 是按名称调用的。按名称作为调用传递的是 add(2, 1)。这将被懒惰地评估。因此控制台上的输出将是“mul”,然后是“add”,尽管似乎首先调用了 add。按名称调用类似于传递函数指针。
现在从 y: => Int 更改为 y: Int。控制台将显示“add”后跟“mul”!常用的评价方式。

于 2017-06-07T20:17:27.760 回答
-1

Scala 变量评估在这里更好地解释了https://sudarshankasar.medium.com/evaluation-rules-in-scala-1ed988776ae8

def main(args: Array[String]): Unit = {
//valVarDeclaration 2
println("****starting the app***") // ****starting the app***
val defVarDeclarationCall1 = defVarDeclaration // defVarDeclaration 1
val defVarDeclarationCall2 = defVarDeclaration // defVarDeclaration 1

val valVarDeclarationCall1 = valVarDeclaration //
val valVarDeclarationCall2 = valVarDeclaration //

val lazyValVarDeclarationCall1 = lazyValVarDeclaration // lazyValVarDeclaration 3
val lazyValVarDeclarationCall2 = lazyValVarDeclaration //

callByValue({
  println("passing the value "+ 10)
  10
}) // passing the value 10
   // call by value example
  // 10

callByName({
  println("passing the value "+ 20)
  20
}) // call by name example
  // passing the value 20
  // 20
  }

  def defVarDeclaration = {
println("defVarDeclaration " + 1)
1
  }

  val valVarDeclaration = {
println("valVarDeclaration " + 2)
2
  }

  lazy val lazyValVarDeclaration = {
println("lazyValVarDeclaration " + 3)
3
  }

  def callByValue(x: Int): Unit = {
println("call by value example ")
println(x)
  }

  def callByName(x: => Int): Unit = {
println("call by name example ")
println(x)
  }

于 2020-12-11T09:38:09.540 回答
-2

我认为这里的所有答案都没有正确的理由:

在按值调用中,参数只计算一次:

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

// following the substitution model

f(12 + 3, 4 * 11)
f(15, 4194304)
15

您可以在上面看到所有参数都被评估是否需要,通常call-by-value可以很快但并不总是像这种情况下那样。

如果评估策略是,call-by-name那么分解将是:

f(12 + 3, 4 * 11)
12 + 3
15

正如您在上面看到的,我们从不需要评估4 * 11,因此节省了一些计算,这有时可能是有益的。

于 2016-08-02T11:48:03.737 回答