将Scala分配评估为单位而不是赋值的动机是什么?
I / O编程中的常见模式是执行以下操作:
while ((bytesRead = in.read(buffer)) != -1) { ...
但这在Scala中是不可能的,因为...
bytesRead = in.read(buffer)
..返回Unit,而不是bytesRead的新值。
忽略功能性语言似乎是一件有趣的事情。我想知道为什么要这么做吗?
将Scala分配评估为单位而不是赋值的动机是什么?
I / O编程中的常见模式是执行以下操作:
while ((bytesRead = in.read(buffer)) != -1) { ...
但这在Scala中是不可能的,因为...
bytesRead = in.read(buffer)
..返回Unit,而不是bytesRead的新值。
忽略功能性语言似乎是一件有趣的事情。我想知道为什么要这么做吗?
Answers:
我主张让赋值返回赋值而不是单位。Martin和我来回地讨论,但是他的论点是在95%的时间中将值放到堆栈上只是为了弹出它是浪费字节码,并且对性能产生负面影响。
void
。如果分配确实在Scala中,foo_=(v: Foo)
则应返回Foo
。
void
(Unit
),作业x = value
被翻译为x.set(value);x.get(value)
;get
如果未使用该值,编译器将在优化阶段中消除-calls。在新的主要版本(由于向后不兼容)的Scala版本中,这可能是一个可喜的变化,并且对用户的烦恼也减少了。你怎么看?
我不了解内幕消息的真实原因,但我的怀疑很简单。Scala难以使用副作用循环,因此程序员自然会偏向于理解。
它以多种方式做到这一点。例如,您没有for
声明和突变变量的循环。while
在测试条件的同时,您不能(轻松)在循环上对状态进行突变,这意味着您经常必须在突变之前和之后重复突变。while
在while
测试条件do { ... } while (...)
中看不到在块内声明的变量,这使它的用处大大减少。等等。
解决方法:
while ({bytesRead = in.read(buffer); bytesRead != -1}) { ...
不管它是值得的。
作为另一种解释,也许马丁·奥德斯基不得不面对源自这种用法的一些非常丑陋的错误,并决定将其从他的语言中取缔。
编辑
大卫·波拉克(David Pollack)回答了一些实际事实,马丁·奥德斯基(Martin Odersky)亲自评论了他的答案,这一事实得到了明显的支持,这使波拉克(Pollack)提出了与绩效相关的问题。
for
环路版本将是:for (bytesRead <- in.read(buffer) if (bytesRead) != -1
这是不同的,它不会工作的伟大,因为没有foreach
和withFilter
可用!
这是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限制可以避免意外使用“ =”而不是“ ==”。
顺便说一句:即使在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
以更隐式的方式进行检查,而不必键入它。