Scala 可以通过引用调用吗?
我知道 Scala 支持 ALGOL 的按名称调用,并且我想我明白这意味着什么,但是 Scala 可以像 C#、VB.NET 和 C++ 那样进行按引用调用吗?我知道 Java 不能进行引用调用,但我不确定这个限制是否完全是由于语言或 JVM 造成的。
当您想要将巨大的数据结构传递给方法,但又不想复制它时,这会很有用。在这种情况下,按引用调用似乎很完美。
I know that Scala supports call-by-name from ALGOL, and I think I understand what that means, but can Scala do call-by-reference like C#, VB.NET, and C++ can? I know that Java cannot do call-by-reference, but I'm unsure if this limitation is solely due to the language or also the JVM.
This would be useful when you want to pass an enormous data structure to a method, but you don't want to make a copy of it. Call-by-reference seems perfect in this case.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
Java 和 Scala 都专门使用按值调用,只不过该值要么是原语,要么是指向对象的指针。如果您的对象包含可变字段,那么这与按引用调用之间几乎没有什么实质性区别。
由于您总是传递指向对象的指针而不是对象本身,因此您不会遇到必须重复复制巨型对象的问题。
顺便说一句,Scala 的按名称调用是使用按值调用来实现的,该值是一个返回表达式结果的函数对象(指向某个函数的指针)。
Java and Scala both use call by value exclusively, except that the value is either a primitive or a pointer to an object. If your object contains mutable fields, then there is very little substantive difference between this and call by reference.
Since you are always passing pointers to objects not the objects themselves, you don't have the problem of having to repeatedly copy a giant object.
Incidentally, Scala's call by name is implemented using call by value, with the value being a (pointer to a) function object that returns the result of the expression.
对于“一切都是对象”并且无法访问对象引用的语言,例如Java和Scala,则每个函数参数都是在该语言下面的某个抽象级别上按值传递的引用。然而,从语言抽象语义的角度来看,要么是按引用调用,要么是按值调用,具体取决于函数是否提供了引用对象的副本。在这种情况下,术语“共享调用”在抽象语言级别上包含“按引用调用”和“按值调用”。因此,可以正确地说 Java 在语言语义以下的抽象级别上是按值调用的(即与假设的将其翻译为 C 或虚拟机字节码的方式进行比较),同时也说 Java 和Scala(除了内置类型)在其“一切都是对象”抽象的语义上是按引用调用的。
在 Java 和 Scala 中,某些内置(a/k/a 原语)类型自动按值传递(例如 int 或 Int),并且每个用户定义的类型都是按引用传递(即必须手动复制它们以仅传递)他们的价值)。
请注意,我更新了维基百科的共享通话部分 让这一点更清楚。
也许维基百科对按值传递和按值调用之间的区别感到困惑?我认为按值传递是更通用的术语,因为它适用于赋值表达式以及函数应用程序。我没有费心去尝试在维基百科上进行修正,把它留给其他人去讨论。
当对象不可变时,按引用调用和按值调用之间在“一切都是对象”的语义级别上没有区别。因此,允许声明按值调用与按引用调用的语言(例如我正在开发的类似 Scala 的语言)可以通过延迟按值复制直到对象被修改来进行优化。
投票否决的人显然不明白什么是“共享通话”。
下面我将添加我为 Copute 语言(针对 JVM)所做的文章,其中我讨论了评估策略。
即使具有纯粹性,也没有图灵完整语言(即允许递归)是完美的声明性的,因为它必须选择一种评估策略。求值策略是函数及其参数之间的相对运行时求值顺序。函数的求值策略可以是严格的,也可以是非严格的,分别与 eager 或 lazy 相同,因为所有表达式都是函数。 Eager 意味着参数表达式在其函数之前被评估;而惰性意味着参数表达式仅在函数中首次使用的运行时时刻计算(一次)。
评估策略决定性能、确定性、调试和操作语义的权衡。对于纯程序,它不会改变指称语义结果,因为在纯粹的情况下,求值顺序的命令式副作用只会导致内存消耗、执行时间、延迟和非终止域中的不确定性(即明确限制) 。
从根本上来说,所有表达式都是函数(的组合),即常量是没有输入的纯函数,一元运算符是具有一个输入的纯函数,二元运算符是具有两个输入的纯函数,构造函数是函数,甚至是控制语句(例如 if、for、 while) 可以用函数建模。我们评估这些函数的顺序不是由语法定义的,例如 f( g() ) 可以在 g 的结果上急切地评估 g,然后评估 f,或者它可以评估 f,并且仅在 f 中需要其结果时才延迟评估 g。
前者(急切)是按值调用(CBV),后者(惰性)是按名称调用(CBN)。 CBV 有一种变体的共享调用,它在 Java、Python、Ruby 等现代 OOP 语言中很流行,其中不纯函数通过引用隐式输入一些可变对象。 CBN 有一个变体按需调用(也是 CBN),其中函数参数仅计算一次(这与记忆函数不同)。几乎总是使用按需要调用而不是按名称调用,因为它的速度呈指数级增长。通常,由于声明的函数层次结构和运行时求值顺序之间的不一致,CBN 的两种变体都只会以纯粹的形式出现。
语言通常具有默认的求值策略,有些语言具有选择性地强制函数以非默认方式求值的语法。默认情况下急切的语言通常会延迟计算布尔连词(a/k/a“and”、&&)和析取操作符(a/k/a“or”、||),因为第二个操作数是一半的情况下不需要,即 true ||任何事物==真与假&&任何东西==假。
For a language where "everything is an object" and the object reference can not be accessed, e.g. Java and Scala, then every function parameter is a reference passed-by-value at some level of abstraction below the language. However, from the perspective of the semantics of language abstraction, there is either a call-by-reference or call-by-value, depending on whether the function is supplied a copy of the referenced object. In this case, the term call-by-sharing encompasses both call-by-reference and call-by-value at the language level of abstraction. Thus it is correct to say that Java is call-by-value at the level of abstraction below the language semantics (i.e. comparing to how it would be hypothetically translated to C or in bytecode for the virtual machine), while also saying that Java and Scala are (except for builtin types) call-by-reference at the semantics of its "everything is an object" abstraction.
In Java and Scala, certain builtin (a/k/a primitive) types get passed-by-value (e.g. int or Int) automatically, and every user defined type is passed-by-reference (i.e. must manually copy them to pass only their value).
Note I updated Wikipedia's Call-by-sharing section to make this more clear.
Perhaps Wikipedia is confused about the distinction between pass-by-value and call-by-value? I thought pass-by-value is the more general term, as it applies to assignment expressions, as well as function application. I didn't bother to try to make that correction at Wikipedia, leave it for others to hash out.
There is no difference at level of semantics where "everything is an object" between call-by-reference and call-by-value, when the object is immutable. Thus a language which allows declaration of call-by-value versus call-by-reference (such as the Scala-like language I am developing), can be optimized by delaying the copy-by-value until the object is modified.
The people who voted this down apparently do not understand what "call-by-sharing" is.
Below I will add the write up I did for my Copute language (which targets JVM), where I discuss evaluation strategy.
Even with purity, no Turing complete language (i.e. that allows recursion) is perfectly declarative, because it must choose an evaluation strategy. Evaluation strategy is the relative runtime evaluation order between functions and their arguments. The evaluation strategy of functions can be strict or non-strict, which is the same as eager or lazy respectively, because all expressions are functions. Eager means argument expressions are evaluated before their function is; whereas, lazy means argument expressions are only evaluated (once) at the runtime moment of their first use in the function.
The evaluation strategy determines a performance, determinism, debugging, and operational semantics tradeoff. For pure programs, it does not alter the denotational semantics result, because with purity, the imperative side-effects of evaluation order only cause indeterminism in (i.e are categorically bounded to) the memory consumption, execution time, latency, and non-termination domains.
Fundamentally all expressions are (composition of) functions, i.e. constants are pure functions without inputs, unary operators are pure functions with one input, binary operators are pure functions with two inputs, constructors are functions, and even control statements (e.g. if, for, while) can be modeled with functions. The order that we evaluate these functions is not defined by the syntax, e.g. f( g() ) could eagerly evaluate g then f on g's result or it could evaluate f and only lazily evaluate g when its result is needed within f.
The former (eager) is call-by-value (CBV) and the latter (lazy) is call-by-name (CBN). CBV has a variant call-by-sharing, which is prevalent in modern OOP languages such as Java, Python, Ruby, etc., where impure functions implicitly input some mutable objects by-reference. CBN has a variant call-by-need (also CBN), where function arguments are only evaluated once (which is not the same as memoizing functions). Call-by-need is nearly always used instead of call-by-name, because it is exponentially faster. Typically both variants of CBN only appear with purity, because of the dissonance between the declared function hierarchy and the runtime order-of-evaluation.
Languages typically have a default evaluation strategy, and some have a syntax to optionally force a function to be evaluated in the non-default. Languages which are eager by default, usually evaluate the boolean conjunction (a/k/a "and", &&) and disjunction (a/k/a "or", ||) operators lazily, because the second operand isn't needed in half the cases, i.e. true || anything == true and false && anything == false.
以下是如何在 Scala 中模拟引用参数。
好吧,这不完全是 Pascal 或 C++ 程序员所习惯的;但 Scala 中很少有这样的情况。好处是,这使调用者可以更灵活地处理发送到参数的值。例如
Here is how to emulate reference parameters in Scala.
Well, okay, not exactly what Pascal or C++ programmers are used to; but then, very little in Scala is. The upside is that this gives the caller more flexibility with what they can do with the value sent to the parameter. E.g.