Scala 传名参数和传值参数
- 传名参数 call-by-name
- 传值参数 call-by-value
在Scala
中,当参数通过传值调用函数时,它会在调用函数之前计算一次传入的表达式或参数值。但是使用传名调用函数时,在函数内部访问参数时会重新计算传入表达式的值。这里的示例显示了它们的差异和语法。也就说传名参数被函数使用时会被重新计算且可能会被计算多次。
传值参数
对形式参数所做的更改不会传回给调用者。任何对被调用函数或方法内部形参变量的修改只影响单独的存储位置,不会反映在调用环境中的实参中。此方法也称为传值调用。
语法
def callByValue(x: Int)
例子
下面是一个函数传值调用的 Scala 程序
object GFG {
// Main方法def main(args: Array[String]) {
// 定义函数def ArticleCounts(i: Int) {
println("Tanya did article on day one is 1 - Total = " + i)println("Tanya did article on day two is 1 - Total = " + i)println("Tanya did article on day three is 1 - Total = " + i)println("Tanya did article on day four is 1 - Total = " + i)}var Total = 0;// 函数调用ArticleCounts {
Total += 1 ; Total}}
}
输出
Tanya did article on day one is 1 - Total = 1
Tanya did article on day two is 1 - Total = 1
Tanya did article on day three is 1 - Total = 1
Tanya did article on day four is 1 - Total = 1
在这里,通过在上面的程序中使用函数传值调用机制,文章总数没有增加。
传名参数
传名调用机制将代码块传递给函数调用,代码块被编译、执行并计算值。消息将首先打印,然后返回值。
语法
def callByName(x: => Int)
例子
下面是一个函数传名调用的 Scala 程序
object main {
// Main方法def main(args: Array[String]) {
// 定义传名调用函数def ArticleCounts(i: => Int) {
println("Tanya did articles on day one is 1 - Total = " + i)println("Tanya did articles on day two is 1 - Total = " + i)println("Tanya did articles on day three is 1 - Total = " + i)println("Tanya did articles on day four is 1 - Total = " + i)}var Total = 0;// 函数调用ArticleCounts {
Total += 1 ; Total}}
}
输出
Tanya did articles on day one is 1 - Total = 1
Tanya did articles on day two is 1 - Total = 2
Tanya did articles on day three is 1 - Total = 3
Tanya did articles on day four is 1 - Total = 4
在这里,通过在上面的程序中使用函数传名调用机制,文章的总数将增加。
对于传名参数,每次使用时都会计算参数值。如果它们未使用,则根本不会对其进行计算。
这类似于用传递的表达式替换传名参数。它们与传值参数相反。要创建一个传名的参数,只需在其类型前面加上=>
。
传名参数的优点是如果它们没有在函数体中使用,它们不会被计算。另一方面,传值参数的优点是它们只被评估一次。
下面是我们如何实现while
循环的示例:
def whileLoop(condition: => Boolean)(body: => Unit): Unit =if (condition) {
bodywhileLoop(condition)(body)}var i = 2whileLoop (i > 0) {
println(i)i -= 1
} // prints 2 1
whileLoop
方法使用多个参数列表来获取循环的condition
和body
。如果condition
为真,则执行body
,然后递归调用whileLoop
。如果condition
为假,则永远不会计算body
,因为我们将=>
附加到body
的类型前面。
现在当我们通过i > 0
作为我们的condition
和println(i); i-= 1
作为body
,它的行为类似于许多语言中的标准while
循环。
如果参数是计算密集型或长时间运行的代码块,如获取URL
,这种延迟计算参数直到它被使用时才计算的能力可以帮助提高性能。
总结
Scala
中的call by name
仅在调用方支持,而lazy
仅在定义方支持。
- 传名参数 call by name
- 参数几乎只是以调用函数时的任何(未计算)形式替换到函数体中。这意味着它可能需要在体内多次计算。
- 按需调用 call by need
- 在调用函数时不会计算参数,但第一次使用时需要,然后值被缓存。之后,每当再次需要该参数时,都会查找缓存的值。scala中使用
lazy val
可以做到这点
- 在调用函数时不会计算参数,但第一次使用时需要,然后值被缓存。之后,每当再次需要该参数时,都会查找缓存的值。scala中使用
- 传值参数 call by value
- 调用函数时会计算参数,即使函数最终没有使用这些参数。一般都是这种方式。
有lazy
为什么还要使用其他方法呢?
除非您具有引用透明性,否则延迟计算很难推理,因为您需要准确确定何时计算您的lazy
值。由于Scala
是为与Java
互操作而构建的,因此它需要支持命令式、副作用编程。因此,在许多情况下,在Scala
中使用lazy
并不是一个好主意。
此外,lazy
有性能开销:您需要有一个额外的间接检查该值是否已经被计算。在Scala
中,这意味着更多的对象,这给垃圾收集器带来了更大的压力。
最后,在某些情况下,惰性计算会留下“空间”泄漏。例如,在Haskell
中,通过将大量数字相加来折叠右侧的大列表是一个坏主意,因为Haskell
会在计算它们之前构建大量对(+)
的惰性调用(实际上你只需要它有一个累加器。即使在简单的上下文中也会遇到空间问题的一个著名例子是foldr vs foldl vs foldl。
其他
如上所示,更常见的使用传名参数方式是这样的:
def foo(x: => Int) = {
lazy val _x = x// 下面都使用 _x
}
这个简单的函数仅会在将使用x
时,对x
进行一次计算,并且使用lazy
存储,这意味着后面使用_x
时,不会再重复计算x
的值了,如果函数不使用_x
,则x
也不会被计算求值。
本文将
evaluated
解释成计算方便理解。
掘金 https://juejin.cn/post/7052590747635646472