Scala 赋值评估为 Unit 而不是赋值的动机是什么?
I/O 编程中的一个常见模式是执行以下操作:
while ((bytesRead = in.read(buffer)) != -1) { ...
但这在 Scala 中是不可能的,因为......
bytesRead = in.read(buffer)
.. 返回 Unit,而不是 bytesRead 的新值。
离开函数式语言似乎是一件有趣的事情。我想知道为什么这样做?
Scala 赋值评估为 Unit 而不是赋值的动机是什么?
I/O 编程中的一个常见模式是执行以下操作:
while ((bytesRead = in.read(buffer)) != -1) { ...
但这在 Scala 中是不可能的,因为......
bytesRead = in.read(buffer)
.. 返回 Unit,而不是 bytesRead 的新值。
离开函数式语言似乎是一件有趣的事情。我想知道为什么这样做?
我主张让作业返回分配的值而不是单位。Martin 和我在这方面反复讨论,但他的论点是在堆栈上放置一个值只是为了在 95% 的时间内将其弹出是浪费字节码并且对性能产生负面影响。
我不知道实际原因的内幕消息,但我的怀疑很简单。Scala 使副作用循环难以使用,因此程序员自然会更喜欢 for-comprehensions。
它以多种方式做到这一点。例如,您没有for
声明和改变变量的循环。在测试条件的同时,您不能(轻松)在while
循环上改变状态,这意味着您经常必须在它之前和结束时重复突变。在块内声明的变量在测试条件下while
是不可见的while
,这使得do { ... } while (...)
它的用处大大降低。等等。
解决方法:
while ({bytesRead = in.read(buffer); bytesRead != -1}) { ...
不管它值多少钱。
作为另一种解释,也许 Martin Odersky 不得不面对源自这种用法的一些非常丑陋的错误,并决定从他的语言中取缔它。
编辑
这是作为 Scala 的一部分发生的,它具有更“形式上正确”的类型系统。从形式上讲,赋值是一个纯粹的副作用语句,因此应该返回Unit
。这确实有一些很好的后果;例如:
class MyBean {
private var internalState: String = _
def state = internalState
def state_=(state: String) = internalState = state
}
该state_=
方法返回Unit
(正如 setter 所期望的那样)正是因为赋值返回Unit
。
我同意对于复制流或类似的 C 样式模式,这个特定的设计决策可能有点麻烦。但是,它实际上总体上相对没有问题,并且确实有助于类型系统的整体一致性。
也许这是由于命令-查询分离原则?
CQS 在 OO 和函数式编程风格的交汇处趋于流行,因为它在具有或不具有副作用(即,改变对象)的对象方法之间创建了明显的区别。将 CQS 应用于变量赋值比平时更进一步,但同样的想法也适用。
CQS 为何有用的简短说明:考虑一种假设的混合 F/OO 语言,其List
类具有方法Sort
、Append
、First
和Length
。在命令式 OO 风格中,可能想要编写这样的函数:
func foo(x):
var list = new List(4, -2, 3, 1)
list.Append(x)
list.Sort()
# list now holds a sorted, five-element list
var smallest = list.First()
return smallest + list.Length()
而在更实用的风格中,人们更有可能写这样的东西:
func bar(x):
var list = new List(4, -2, 3, 1)
var smallest = list.Append(x).Sort().First()
# list still holds an unsorted, four-element list
return smallest + list.Length()
这些似乎试图做同样的事情,但显然两者之一是不正确的,并且在不了解更多关于方法的行为的情况下,我们无法判断哪一个。
但是,使用 CQS,我们会坚持如果Append
和Sort
更改列表,它们必须返回单元类型,从而防止我们在不应该使用第二种形式时创建错误。因此,副作用的存在也隐含在方法签名中。
我猜这是为了保持程序/语言没有副作用。
你所描述的是故意使用副作用,在一般情况下被认为是一件坏事。
将赋值用作布尔表达式并不是最好的风格。您同时执行两件事,这通常会导致错误。Scalas 限制避免了意外使用“=”而不是“==”。
顺便说一句:我发现最初的 while-trick 很愚蠢,即使在 Java 中也是如此。为什么不这样呢?
for(int bytesRead = in.read(buffer); bytesRead != -1; bytesRead = in.read(buffer)) {
//do something
}
当然,分配出现了两次,但至少 bytesRead 在它所属的范围内,而且我不是在玩有趣的分配技巧......
只要您有间接引用类型,您就可以有一个解决方法。在幼稚的实现中,您可以将以下内容用于任意类型。
case class Ref[T](var value: T) {
def := (newval: => T)(pred: T => Boolean): Boolean = {
this.value = newval
pred(this.value)
}
}
ref.value
然后,在您之后必须用来访问引用的约束下,您可以将while
谓词编写为
val bytesRead = Ref(0) // maybe there is a way to get rid of this line
while ((bytesRead := in.read(buffer)) (_ != -1)) { // ...
println(bytesRead.value)
}
并且您可以bytesRead
以更隐式的方式进行检查,而无需键入它。