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

时间:2012-11-12 01:33:15

标签: scala

据我了解,在Scala中,函数可以被称为

  • 按值或
  • 通过名字

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

声明:

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

呼叫

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

请问有什么规定?

16 个答案:

答案 0 :(得分:485)

您给出的示例仅使用call-by-value,因此我将给出一个新的,更简单的示例,以显示差异。

首先,我们假设我们有一个带副作用的函数。此函数打印出一些内容,然后返回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())的副作用只发生过一次。但是,在按名称调用版本中,副作用发生了两次。

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

答案 1 :(得分:47)

以下是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个
结果在同一步骤内达成。

答案 2 :(得分:14)

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

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

这样,参数x将不会被评估,直到在函数中调用它。

这里little post也很好地解释了这一点。

答案 3 :(得分:7)

为了在上述评论中反映@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

答案 4 :(得分:6)

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

想象一下,你想建立一个"唠叨的应用程序" ,每次你被唠叨的时候都会给你一个Nag。

检查以下实施:

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仅在通过名称传递时才起作用 原因是,当通过值时,它将重新使用,因此不会重新评估该值,而当通过名称时,每次访问变量时都会重新评估该值

答案 5 :(得分: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. scala Test
 3. In delayed method
 4. Getting time in nano seconds
 5. Param: 81303808765843
 6. Getting time in nano seconds

答案 6 :(得分:2)

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

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

Higher-Order-Functions

此函数包含一个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的值时,会调用callByName方法的定义。

答案 7 :(得分:2)

按值分类是一般用例,这里有许多答案解释..

  

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

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

示例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 (with scala) uses logging using call by name way see Logging trait 懒惰地评估 是否log.isInfoEnabled来自以下方法。

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

答案 8 :(得分:2)

这里是一个简短的示例,我编写了一个代码来帮助我的一位正在学习Scala课程的同事。我认为很有趣的是,马丁没有使用讲座前面介绍的&&问题作为示例。无论如何,我希望这会有所帮助。

<link rel="stylesheet" type="text/css" href="cssFileName.css">

代码输出如下:

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)

答案 9 :(得分: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) -> ... 

答案 10 :(得分:1)

通过一个例子可以帮助你更好地理解差异。

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

def getTime = System.currentTimeMillis

现在,我们将通过名称定义一个函数,该函数会延迟两次打印一次:

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

结果应该解释差异。该代码段可用here

答案 11 :(得分:1)

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

按名称调用中,表达式本身作为参数传递给函数,只有在调用特定参数时才会在函数内部计算。

使用以下示例可以更好地理解Scala中的Call by Name和Call by Value之间的区别:

代码段

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函数的函数定义,其中有一个 =&gt; 符号,用于分隔参数 x 及其数据类型。那个特定符号表示该函数是按名称类型调用的。

换句话说,值函数参数的调用在进入函数之前被计算一次,但是只有在需要函数时才会在函数内部计算名称函数参数的调用。

希望这有帮助!

答案 12 :(得分: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函数中使用循环函数。 / p>

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

一个callByValue函数将loop方法作为一个参数作为参数,因为函数内部函数或表达式在执行外部函数之前被评估loop函数递归执行而我们永远不会得到{{ 1}}提示回来。

scala

答案 13 :(得分: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:=&gt; Int是按名称调用的。按名称传递的内容是add(2,1)。这将被懒惰地评估。因此控制台上的输出将是“mul”,然后是“add”,尽管add似乎首先被调用。按名称调用是一种传递函数指针的方式 现在改变y:=&gt; Int到y:Int。控制台将显示“添加”,然后显示“mul”!通常的评价方式。

答案 14 :(得分:-1)

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

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

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,因此节省了一些有时可能有用的计算。

答案 15 :(得分:-1)

这里更好地解释了标量变量评估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)
  }