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

Jam*_*sev 226 scala

据我所知,在Scala中,也可以调用函数

  • 按值或
  • 按名字

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

宣言:

def  f (x:Int, y:Int) = x;
Run Code Online (Sandbox Code Playgroud)

呼叫

f (1,2)
f (23+55,5)
f (12+3, 44*11)
Run Code Online (Sandbox Code Playgroud)

请问有什么规定?

dhg*_*dhg 507

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

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

def something() = {
  println("calling something")
  1 // return value
}
Run Code Online (Sandbox Code Playgroud)

现在,我们要定义两个函数,接受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)
}
Run Code Online (Sandbox Code Playgroud)

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

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

scala> callByName(something())
calling something
x1=1
calling something
x2=1
Run Code Online (Sandbox Code Playgroud)

所以,你可以看到,在呼叫按值版本,传入的函数调用(的副作用something())只发生过一次.但是,在按名称调用版本中,副作用发生了两次.

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

  • 我一直认为这个术语不必要地混淆.一个函数可以有多个参数,这些参数的名称调用与按值调用状态不同.因此,不是*function*是按名称调用或按值调用,而是它的每个*参数*可以**通过** - 按名称或按值传递.此外,"按名称呼叫"与**名称**无关.`=> Int`是来自`Int`的不同的**类型**; 它是"没有参数的函数,它将生成一个`Int`"而不仅仅是`Int`.一旦你获得了一流的功能,你就不需要**来发明用于描述它的名字命名术语. (281认同)
  • @TimGoodman你是对的,它比我做的要复杂一点.`=> Int`是一种方便,并没有像函数对象那样完全实现(大概是为什么你不能有`=> Int`类型的变量,尽管没有根本原因导致这不起作用) .`()=> Int`是*显式*是一个没有参数的函数,它将返回一个`Int`,它需要被显式调用并且可以作为函数传递.`=> Int`是一种"代理`Int`",你唯一可以做的就是调用它(隐式)来获取`Int`. (5认同)
  • @Ben所以如果`=> Int`是"无参数生成Int的函数",它与`(=)= Int`的区别?Scala似乎对待这些不同,例如`=> Int`显然不能作为`val`的类型,只作为参数的类型. (4认同)
  • @SelimOber如果将文本`f(2)`编译为`Int`类型的表达式,则生成的代码使用参数`2`调用`f`,结果是表达式的值.如果将相同的文本编译为类型`=> Int`的表达式,则生成的代码使用对某种"代码块"的引用作为表达式的值.无论哪种方式,该类型的值都可以传递给期望该类型参数的函数.我很确定你可以通过变量赋值做到这一点,没有参数传递.那么名字或者召唤与它有什么关系呢? (3认同)
  • @Ben,这有助于回答几个问题,谢谢.我希望更多的文章清楚地解释了名字传递的语义. (2认同)

小智 50

以下是Martin Odersky的一个例子:

def test (x:Int, y: Int)= x*x
Run Code Online (Sandbox Code Playgroud)

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

test (2,3)
Run Code Online (Sandbox Code Playgroud)

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

test (3+4,8)
Run Code Online (Sandbox Code Playgroud)

按值呼叫:测试(7,8) - > 7*7 - > 49
呼叫按名称:(3 + 4)(3 + 4) - > 7(3 + 4) - > 7*7 - > 49
这里呼叫按价值更快.

test (7,2*4)
Run Code Online (Sandbox Code Playgroud)

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

test (3+4, 2*4) 
Run Code Online (Sandbox Code Playgroud)

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


res*_*a87 15

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

def f(x: => Int, y:Int) = x
Run Code Online (Sandbox Code Playgroud)

这样,在函数中调用参数之前,x不会对参数进行求值.

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


use*_*984 9

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

实际上,而不是定义

def callByName(x: => Int) = {
  println("x1=" + x)
  println("x2=" + x)
}
Run Code Online (Sandbox Code Playgroud)

和运行:

scala> callByName(something())
calling something
x1=1
calling something
x2=1
Run Code Online (Sandbox Code Playgroud)

你也可以这样写:

def callAlsoByName(x: () => Int) = {
  println("x1=" + x())
  println("x2=" + x())
}
Run Code Online (Sandbox Code Playgroud)

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

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

calling something
x1=1
calling something
x2=1
Run Code Online (Sandbox Code Playgroud)

  • 我最近也遇到了这个问题。从概念上这样想是可以的,但是 scala 区分了 `=> T` 和 `() => T`。采用第一个类型作为参数的函数不会接受第二个类型,scala 在“@ScalaSignature”注释中存储了足够的信息,以便为此抛出编译时错误。不过,“=> T”和“() => T”的字节码是相同的,都是“Function0”。有关更多详细信息,请参阅[此问题](http://stackoverflow.com/questions/40251514/how-does-scala-distinguish- Between-t-and-t )。 (2认同)

小智 6

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())
    }
}
Run Code Online (Sandbox Code Playgroud)

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