提问人:Graham Lea 提问时间:1/4/2010 更新时间:6/19/2010 访问量:8532
Scala 赋值评估给 Unit 而不是赋值的动机是什么?
What is the motivation for Scala assignment evaluating to Unit rather than the value assigned?
问:
Scala 赋值评估给 Unit 而不是赋值的动机是什么?
I/O 编程中的常见模式是执行如下操作:
while ((bytesRead = in.read(buffer)) != -1) { ...
但这在 Scala 中是不可能的,因为......
bytesRead = in.read(buffer)
..返回 Unit,而不是 bytesRead 的新值。
从函数式语言中省略似乎是一件有趣的事情。 我想知道为什么要这样做?
答:
我猜这是为了让程序/语言没有副作用。
你所描述的是故意使用副作用,这在一般情况下被认为是一件坏事。
评论
val a = b = 1
val
b
val a = 1; val b = 1;
将赋值用作布尔表达式不是最佳样式。你同时执行两件事,这经常会导致错误。并且通过 Scalas 限制避免了意外使用“=”而不是“==”。
评论
我不了解实际原因的内幕消息,但我的怀疑很简单。Scala 使副作用循环使用起来很笨拙,因此程序员自然会更喜欢理解。
它以多种方式做到这一点。例如,你没有一个循环来声明和改变一个变量。你不能(轻易地)在测试条件的同时改变循环上的状态,这意味着你经常必须在它之前和结束时重复突变。在块内声明的变量在测试条件中不可见,这使得它的用处要小得多。等等。for
while
while
while
do { ... } while (...)
解决方法:
while ({bytesRead = in.read(buffer); bytesRead != -1}) { ...
无论它值多少钱。
作为另一种解释,也许马丁·奥德斯基(Martin Odersky)不得不面对这种用法产生的一些非常丑陋的错误,并决定从他的语言中取缔它。
编辑
大卫·波拉克(David Pollack)用一些实际事实来回答,马丁·奥德斯基(Martin Odersky)本人评论了他的答案,这一事实清楚地证实了波拉克提出的与绩效有关的问题论点。
评论
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
}
该方法返回(正如 setter 所期望的那样),正是因为 assignment 返回 .state_=
Unit
Unit
我同意,对于像复制流或类似 C 样式的模式,这个特定的设计决策可能有点麻烦。然而,它实际上总体上是相对没有问题的,并且确实有助于类型系统的整体一致性。
评论
我主张让赋值返回分配的值而不是单位。Martin 和我来来回回地讨论这个问题,但他的论点是,在堆栈上放置一个值只是为了在 95% 的时间内将其弹出,这是对字节码的浪费,并且会对性能产生负面影响。
评论
void
foo_=(v: Foo)
Foo
void
Unit
x = value
x.set(value);x.get(value)
get
也许这是由于命令-查询分离原则?
CQS 往往在面向对象和函数式编程风格的交集上很受欢迎,因为它在有或没有副作用(即改变对象)的对象方法之间创建了明显的区别。将 CQS 应用于变量赋值比平时更进一步,但同样的想法也适用。
CQS 为何有用的简短说明:考虑一个假设的混合 F/OO 语言,其类具有方法 、 、 和 。在命令式 OO 风格中,人们可能想要编写一个这样的函数:List
Sort
Append
First
Length
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
顺便说一句:我发现最初的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
下一个:标量与列表赋值运算符
评论