对于“一切都是对象”并且无法访问对象引用的语言,例如Java 和 Scala,那么每个函数参数都是在语言下面的某种抽象级别上按值传递的引用。然而,从语言抽象语义的角度来看,有引用调用还是值调用,这取决于是否为函数提供了引用对象的副本。在这种情况下,共享调用一词在抽象语言级别包括引用调用和值调用。因此,可以说 Java 在语言语义之下的抽象级别上是按值调用的(即与假设将其翻译为 C 或虚拟机字节码的方式进行比较),同时还说 Java 和Scala(除了内置类型)在其“一切都是对象”抽象的语义上是按引用调用的。
在 Java 和 Scala 中,某些内置(a/k/a 原语)类型会自动按值传递(例如 int 或 Int),并且每个用户定义的类型都通过引用传递(即必须手动复制它们只传递它们的值)。
请注意,我更新了 Wikipedia 的 Call-by-sharing section 以使其更加清晰。
也许 Wikipedia 对 pass-by-value 和 call-by-value 之间的区别感到困惑?我认为按值传递是更通用的术语,因为它适用于赋值表达式以及函数应用程序。我没有费心去尝试在 Wikipedia 上进行更正,留给其他人讨论吧。
当对象是不可变的时,在按引用调用和按值调用之间“一切都是对象”的语义级别没有区别。因此,一种允许按值调用与按引用调用的语言(例如我正在开发的类似 Scala 的语言)可以通过延迟按值复制直到对象被修改来进行优化。
投反对票的人显然不明白什么是“共享电话”。
下面我将添加我为我的 Copute 语言(针对 JVM)所做的文章,我将在其中讨论评估策略。
即使具有纯度,也没有图灵完备的语言(即允许递归)是完美的声明性语言,因为它必须选择评估策略。评估策略是函数及其参数之间的相对运行时评估顺序。函数的求值策略可以是严格的,也可以是非严格的,分别与eager或lazy相同,因为所有表达式都是函数。 Eager 意味着参数表达式在其函数之前被评估;而惰性表示参数表达式仅在函数第一次使用的运行时被评估(一次)。
评估策略决定了性能、确定性、调试和操作语义的权衡。对于纯程序,它不会改变指称语义结果,因为在纯度方面,评估顺序的命令性副作用只会导致内存消耗、执行时间、延迟和非终止域的不确定性(即绝对有界) .
从根本上说,所有表达式都是函数的(组合),即常量是没有输入的纯函数,一元运算符是具有一个输入的纯函数,二元运算符是具有两个输入的纯函数,构造函数是函数,甚至是控制语句(例如,如果, for, while) 可以用函数建模。我们评估这些函数的顺序不是由语法定义的,例如f( g() ) 可以在 g 的结果上急切地评估 g 然后 f 或者它可以评估 f 并且只在 f 中需要它的结果时才懒惰地评估 g。
前者(eager)是按值调用(CBV),后者(懒惰)是按名称调用(CBN)。 CBV 有一个变种 call-by-sharing,这在现代 OOP 语言如 Java、Python、Ruby 等中很普遍,其中不纯函数通过引用隐式输入一些可变对象。 CBN 有一个变体 call-by-need(也是 CBN),其中函数参数只计算一次(这与记忆函数不同)。几乎总是使用按需调用而不是按名称调用,因为它的速度呈指数级增长。通常,由于声明的函数层次结构和运行时求值顺序之间的不协调,CBN 的两种变体都只会以纯度出现。
语言通常有一个默认的评估策略,有些语言有一个语法可以选择性地强制一个函数在非默认的情况下被评估。默认情况下渴望的语言通常会懒惰地评估布尔连词(a/k/a“and”,&&)和析取(a/k/a“or”,||)运算符,因为不需要第二个操作数在一半的情况下,即正确||任何东西 == 真假 && 任何东西 == 假。