scala中的call-by-name和call-by-value

2024-08-24 10:58
文章标签 call scala value

本文主要是介绍scala中的call-by-name和call-by-value,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

scala中的call-by-name和call-by-value

var/def/val/lazy val

  • def:类似于每一次重新赋值,如果是用def定义函数,则是每一次重新获得一个函数,做call-by-name操作。
  • val:获得一次,并立即执行,且在生命周期内不能再被修改,使用的是call-by-value操作。
  • var:在生命周期内可以被再次赋值
  • lazy val:惰性执行,也就是赋值(绑定)的时候先不会执行,等到需要的时候再执行

例子

scala> def f = {println("hello"); 1.0}
f: Doublescala> f
hello
res3: Double = 1.0scala> f
hello
res4: Double = 1.0scala> f
hello
res5: Double = 1.0

这里使用的是def,则每次使用的时候相当于获取一个新的函数。
体现是,每一次使用f的时候,都会获得一个{ println(“hello”); 1.0},其返回值为1,但是中间过程会打印出hello,这是在赋值(绑定)的时候才会打印出来的。

scala> val f = { println("hello"); 1.0}
hello
f: Double = 1.0scala> f
res6: Double = 1.0scala> f
res7: Double = 1.0

这里使用val,则只有一次绑定,所以后面不再打印出hello。如果觉得迷糊,可以继续看下去。

scala> lazy val f = { println("hello"); 1.0}
f: Double = <lazy>scala> f
hello
res8: Double = 1.0scala> f
res9: Double = 1.0

这里使用的是lazy val,即为惰性求值的。可以看到在进行绑定的时候并没有打印出hello,也看不到其值,是因为只是定义了这个值。

在第二次进行使用的时候则会真正去计算这个值(类似于val),第一次会打印出hello,之后就不再打印出了

函数定义中的call-by-name和call-by-value

看了上面的讲解还觉得迷糊的,就是不太理解call-by-name和call-by-value。

scala的call by name 和call by value最大的区别就是:

call-by-name在调用的时候会重新根据name做计算,而call-by-value预先计算,然后保留计算值后一直使用这个value。

纯函数例子[1]

一个Stack Overflow的例子。

def something() = {println("calling something")1 // return value
}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=1scala> callByName(something())
calling something
x1=1
calling something
x2=1

这是因为在调用函数之前,call-by-value会先计算传入的表达式的值,因此每次都访问相同的值,且也不会再次调用。

但是,call-by-name,每次应用的时候,都会重新计算传入的表达式的值。

不纯函数的例子[2]

上的都是纯函数,下面写一个非纯函数可能会更加明显。

举一个例子,假设有一只酒鬼,他最初有十元钱,每天喝酒都会花掉一元钱。设他有一个技能是数自己的钱,返回每天他口袋里钱的最新数目。

object Drunkard {//最开始拥有的软妹币  var money = 10//每天喝掉一个软妹币  def drink: Unit = {money -= 1}//数钱时要算上被喝掉的软妹币  def count: Int = {drinkmoney}//每天都数钱  def printByName(x: => Int): Unit = {for (i <- 0 until 5)println("每天算一算,酒鬼还剩" + x + "块钱!")}//第一天数一下记墙上,以后每天看墙上的余额  def printByValue(x: Int): Unit = {for (i <- 0 until 5)println("只算第一天,酒鬼还剩" + x + "块钱!")}def main(args: Array[String]) = {printByName(count)printByValue(count)}
}
/*
每天算一算,酒鬼还剩9块钱!  
每天算一算,酒鬼还剩8块钱!  
每天算一算,酒鬼还剩7块钱!  
每天算一算,酒鬼还剩6块钱!  
每天算一算,酒鬼还剩5块钱!  
只算第一天,酒鬼还剩4块钱!  
只算第一天,酒鬼还剩4块钱!  
只算第一天,酒鬼还剩4块钱!  
只算第一天,酒鬼还剩4块钱!  
只算第一天,酒鬼还剩4块钱!
*/  

可以看到,酒鬼最初5天每天都会数一下口袋里的软妹币(call-by-name),得到了每天喝酒花钱之后剩下的软妹币数量。

他觉得麻烦,于是想出了一个聪明的方法,在第六天的时候,他将口袋里还剩下的余额数写在了墙上(call-by-value),以后每天看一下墙上的数字,就知道自己还剩多少钱了。

死循环例子

我们可以写出一个函数递归的”循环”。

如果在call-by-name下会停止,并不表示着在call-by-value下会停止。
但是,如果call-by-value停止了,call-by-name一定会停止。

// 这是一个死循环def loop: Boolean = loop// 用val定义时会做call-by-value,以下语句会block住val x = loop // 用def定义时,是做的call-by-name。故以下语句暂时不会执行,在用到y的时候才做evaluationdef y = loop

两者的比较

call-by-value在进入函数体之前就对参数表达式进行了计算,这避免了函数内部多次使用参数时重复计算其值,在一定程度上提高了效率。

但是call-by-name的一个优势在于,如果参数在函数体内部没有被使用到,那么它就不用计算参数表达式的值了。在这种情况下,call-by-name的效率会高一点。

object Main {def useOrNotUse(flag: Boolean, x: Int, y: => Int) = {if (flag) {x}else {x + y}}def main(args: Array[String]) = {val flag = trueprintln(useOrNotUse(flag, 1, 2))val flag = falseprintln(useOrNotUse(flag, 1, 2))}
}  

参考资料

  1. Call by name vs call by value in Scala, clarification needed
  2. scala中的var,val,immutable,mutable理解小结
  3. scala中的call-by-name和call-by-value

这篇关于scala中的call-by-name和call-by-value的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1102282

相关文章

AutoGen Function Call 函数调用解析(一)

目录 一、AutoGen Function Call 1.1 register_for_llm 注册调用 1.2 register_for_execution 注册执行 1.3 三种注册方法 1.3.1 函数定义和注册分开 1.3.2 定义函数时注册 1.3.3  register_function 函数注册 二、实例 本文主要对 AutoGen Function Call

Python __call__ 用法 作用

当一个对象为可被调用对象时,callable(object)返回为True,否则为False: Python中的对象有可被调用和不可被调用之分。 def func_test():print("func_test run")class class_test():def __init__(self):pass# func_test is callable return Trueprint("fu

为 Key-Value 数据库实现MVCC 事务

ACID是软件领域使用最广泛的技术之一,它是关系数据库的基石,是企业级中间件不可或缺的部分,但通常通过黑盒的方式提供。但是在许多情况下,这种古老的事务方式已经不能够适应现代大规模系统和NoSQL数据库的需要了,现代系统要求更高的性能要求,更大的数据量,更高的可用性。在这种情况下,传统的事务模型被定制的事务或者半事务模型所取代,而在这些模型中事务性并不像以往那样被看重。   在本文中我们会讨论一

兔子-(PHP 5.3 and above) Please set 'request_order' ini value to include C,G and P (recommended: 'CGP'

由于在PHP最新的版本中增加了一个配置项目“request_order”,默认值为“GP”,这个存在一定的安全风险。这里我们建议用户将配置更改为“CGP” 可以在php的安装目录下找到php.ini配置目录,找到下面选项: request_order = "GP"  更改为 request_order = "CGP"   重启服务器后即可。 此

MySql 1264 - Out of range value for column 异常

前段时间操作数据库,本是一个很简单的修改语句,却报了  1264 - Out of range value for column字段类型官网  当时一看懵逼了,网上很多都说是配置的问题,需要修改my.ini文件,这个方式我没有试过,我想肯定还有其它方法,经过慢慢排 查发现表里的字段为 decimal(10,3) ,这说明小数点前只有7位,保留了3位小数点,而值在小数点前却有8位,这就导致了错误

【scala 安装错误】错误: 找不到或无法加载主类 scala.tools.nsc.MainGenericRunner

错误: 找不到或无法加载主类 scala.tools.nsc.MainGenericRunner 原因: Scala安装路径中包含空格。 解决办法:scala 不要安装在E:\Program Files 这种有空格的目录下,简直坑

Scala模式匹配下提取器构造

示例代码: object :> {def unapply[A] (list:List[A]) = {Some( (list.init,list.last) )}}object Extractor_Advanced {def main(args: Array[String]): Unit = {(1 to 9).toList match{ case _ :> 9 => println(

从spark源码的角度思考scala中的模式匹配

1.scala中模式匹配 2.spark源码中的模式匹配思考 spark中master会收到worker发过来的akka的消息, 此消息是case class即(Master.class中): case class RegisterWorker(id:String,host:String,port:Int,cores:Int,memory:Int,webUiPort:int

Scala界面事件处理

示例代码: import scala.swing.SimpleSwingApplicationimport scala.swing.MainFrameimport scala.swing.Buttonimport scala.swing.Labelimport scala.swing.Orientationimport scala.swing.BoxPanelimpo

Scala界面Panel、Layout初探

示例代码: package com.dt.scala.guiimport scala.swing.SimpleSwingApplicationimport scala.swing.MainFrameimport scala.swing.Buttonimport scala.swing.Labelimport scala.swing.Orientationimport scal