根据我的理解,在Scala中,函数也可以被调用

传递或 的名字

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

声明:

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

Call

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

请问规则是什么?


您给出的示例只使用了按值调用,因此我将给出一个新的、更简单的示例来显示两者的区别。

首先,让我们假设我们有一个带有副作用的函数。这个函数输出一些内容,然后返回一个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())的副作用只发生了一次。然而,在叫名字的版本中,副作用发生了两次。

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


在你的例子中,所有的参数都将在函数中调用之前被求值,因为你只是通过值定义它们。 如果你想通过名称定义参数,你应该传递一个代码块:

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

这样,在函数中调用参数x之前,将不会计算参数x。

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


下面是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 结果是在相同的步骤中达到的。


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

想象一下,你想要创建一个“唠叨应用程序”,每当你被唠叨时,它就会唠叨你。

检查以下实现:

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只在通过名称传递时才会工作 原因是,当通过值传递时,它将被重用,因此值将不会被重新计算,而当通过名称传递时,值将在每次访问变量时重新计算


为了重复@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

正如我假设的那样,上面讨论的按值调用函数只是将值传递给函数。根据Martin Odersky的说法,这是Scala遵循的一种评估策略,在函数评估中扮演着重要的角色。但是,让叫名字变得简单。这就像将函数作为参数传递给方法(也称为高阶函数)。当方法访问传递参数的值时,它调用传递函数的实现。如下:

根据@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。但在按值调用的情况下,它只将整数值传递给函数。我们将函数调用如下:

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

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


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

例如:-

我有一个无限循环,即如果你执行这个函数,我们将永远不会得到scala提示。

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

callByName函数接受上述循环方法作为参数,并且从不在函数体中使用。

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函数将上述循环方法作为参数,因为函数内部的结果或表达式在执行外部函数之前被递归执行的循环函数求值,并且我们永远不会得到scala提示。

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

scala> callByValue(1,loop(1))

通常,函数的参数是值形参;也就是说,参数的值是在传递给函数之前确定的。但是,如果我们需要编写一个函数,该函数接受一个表达式作为参数,而我们不希望在函数内调用该表达式时才计算该表达式,该怎么办?对于这种情况,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

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

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

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

// following the substitution model

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

你可以在上面看到,不管是否需要,所有的参数都会被求值,通常按值调用可以很快,但并不总是像这种情况。

如果评估策略是按名称调用的,那么分解将是:

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

正如你所看到的,我们从来不需要计算4 * 11,因此节省了一些计算,这有时可能是有益的。


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

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

// 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) -> ... 

看到这个:

    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”!通常的评估方法。


在互联网上已经有很多关于这个问题的精彩答案。我将把我收集到的关于这个主题的一些解释和例子汇编在一起,以防有人会觉得有用

介绍

cb v值()都未

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

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

call-by-name (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

名称调用机制将一个代码块传递给调用,每次调用访问参数时,都会执行该代码块并计算该值。在下面的示例中,delayed会打印一条消息,表明方法已被输入。接下来,delayed打印带有其值的消息。最后,延迟返回' 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求值也终止 -另一个方向是不对的

Non-termination例子

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

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

CBN:第一次(1,循环)→1 CBV: first(1,loop)→该表达式的约简参数。因为一个是循环,它可以无限地减少参数。它不会终止

每种情况下行为的差异

让我们定义一个方法测试

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

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

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

案例4测试(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: => 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())的副作用只发生了一次。然而,在叫名字的版本中,副作用发生了两次。

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

最好使用名字调用的例子

来自: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
}

按值调用是常见的用例,这里有很多答案。

名称调用将一个代码块传递给调用方,每次调用时 调用方访问参数,执行代码块,然后 值被计算。

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

示例1:

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

 /**
   * 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一起)使用按名称调用的方式使用日志记录,参见日志特性 其中其惰性计算是否日志。isInfoEnabled或否。

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

通过一个例子可以帮助您更好地理解其中的区别。

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

def getTime = System.currentTimeMillis

现在我们将定义一个函数,通过名称,打印两次延迟一秒的时间:

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

一个值为1:

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

现在让我们分别调用:

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

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

结果应该可以解释这种差异。这个代码片段可以在这里找到。


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

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

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及其数据类型。这里的特定符号表明该函数是按名称调用类型的。

换句话说,按值调用的函数实参在进入函数之前求值一次,而按名称调用的函数实参仅在需要时才在函数内部求值。

希望这能有所帮助!


下面是我编写的一个快速示例,以帮助我的一位正在学习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
========================

Scala变量计算在better 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) }