84

Scala 赋值评估为 Unit 而不是赋值的动机是什么?

I/O 编程中的一个常见模式是执行以下操作:

while ((bytesRead = in.read(buffer)) != -1) { ...

但这在 Scala 中是不可能的,因为......

bytesRead = in.read(buffer)

.. 返回 Unit,而不是 bytesRead 的新值。

离开函数式语言似乎是一件有趣的事情。我想知道为什么这样做?

4

8 回答 8

91

我主张让作业返回分配的值而不是单位。Martin 和我在这方面反复讨论,但他的论点是在堆栈上放置一个值只是为了在 95% 的时间内将其弹出是浪费字节码并且对性能产生负面影响。

于 2010-01-04T16:18:56.477 回答
21

我不知道实际原因的内幕消息,但我的怀疑很简单。Scala 使副作用循环难以使用,因此程序员自然会更喜欢 for-comprehensions。

它以多种方式做到这一点。例如,您没有for声明和改变变量的循环。在测试条件的同时,您不能(轻松)在while循环上改变状态,这意味着您经常必须在它之前和结束时重复突变。在块内声明的变量在测试条件下while是不可见的while,这使得do { ... } while (...)它的用处大大降低。等等。

解决方法:

while ({bytesRead = in.read(buffer); bytesRead != -1}) { ... 

不管它值多少钱。

作为另一种解释,也许 Martin Odersky 不得不面对源自这种用法的一些非常丑陋的错误,并决定从他的语言中取缔它。

编辑

大卫波拉克回答了一些实际事实,马丁奥德斯基本人评论了他的答案这一事实清楚地支持了波拉克提出的与性能相关的问题论点。

于 2010-01-04T11:57:03.503 回答
12

这是作为 Scala 的一部分发生的,它具有更“形式上正确”的类型系统。从形式上讲,赋值是一个纯粹的副作用语句,因此应该返回Unit。这确实有一些很好的后果;例如:

class MyBean {
  private var internalState: String = _

  def state = internalState

  def state_=(state: String) = internalState = state
}

state_=方法返回Unit(正如 setter 所期望的那样)正是因为赋值返回Unit

我同意对于复制流或类似的 C 样式模式,这个特定的设计决策可能有点麻烦。但是,它实际上总体上相对没有问题,并且确实有助于类型系统的整体一致性。

于 2010-01-04T15:14:46.050 回答
7

也许这是由于命令-查询分离原则?

CQS 在 OO 和函数式编程风格的交汇处趋于流行,因为它在具有或不具有副作用(即,改变对象)的对象方法之间创建了明显的区别。将 CQS 应用于变量赋值比平时更进一步,但同样的想法也适用。

CQS 为何有用的简短说明:考虑一种假设的混合 F/OO 语言,其List类具有方法SortAppendFirstLength。在命令式 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,我们会坚持如果AppendSort更改列表,它们必须返回单元类型,从而防止我们在不应该使用第二种形式时创建错误。因此,副作用的存在也隐含在方法签名中。

于 2010-01-04T16:38:11.257 回答
4

我猜这是为了保持程序/语言没有副作用。

你所描述的是故意使用副作用,在一般情况下被认为是一件坏事。

于 2010-01-04T10:42:49.983 回答
4

将赋值用作布尔表达式并不是最好的风格。您同时执行两件事,这通常会导致错误。Scalas 限制避免了意外使用“=”而不是“==”。

于 2010-01-04T11:08:41.380 回答
2

顺便说一句:我发现最初的 while-trick 很愚蠢,即使在 Java 中也是如此。为什么不这样呢?

for(int bytesRead = in.read(buffer); bytesRead != -1; bytesRead = in.read(buffer)) {
   //do something 
}

当然,分配出现了两次,但至少 bytesRead 在它所属的范围内,而且我不是在玩有趣的分配技巧......

于 2010-01-04T21:55:21.803 回答
0

只要您有间接引用类型,您就可以有一个解决方法。在幼稚的实现中,您可以将以下内容用于任意类型。

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以更隐式的方式进行检查,而无需键入它。

于 2010-06-18T23:03:17.453 回答