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

传递或 的名字

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

声明:

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

Call

f (1,2)
f (23+55,5)
f (12+3, 44*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 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只在通过名称传递时才会工作 原因是,当通过值传递时,它将被重用,因此值将不会被重新计算,而当通过名称传递时,值将在每次访问变量时重新计算

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

下面是我编写的一个快速示例,以帮助我的一位正在学习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提供了名称调用参数。

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

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

正如我假设的那样,上面讨论的按值调用函数只是将值传递给函数。根据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方法的定义。