Scala分配评估单元而不是分配的值的动机是什么?

时间:2010-01-04 10:37:35

标签: scala functional-programming io assignment-operator

Scala分配评估单位的动机是什么,而不是分配的值?

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

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

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

bytesRead = in.read(buffer)

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

从功能语言中省略,似乎是一件有趣的事情。 我想知道为什么会这样做?

8 个答案:

答案 0 :(得分:79)

我主张让作业返回分配的值而不是单位。马丁和我在它上面来回走动,但他的论点是,在95%的时间内将值放在堆栈上只是浪费字节码并对性能产生负面影响。

答案 1 :(得分:19)

我不知道有关实际原因的内幕消息,但我的怀疑非常简单。 Scala使得副作用循环难以使用,因此程序员自然会更喜欢理解。

它在很多方面都是这样做的。例如,您没有声明和变异变量的for循环。在测试条件的同时,您不能(轻松)在while循环上变异状态,这意味着您经常需要在它之前和结尾处重复变异。 while块中声明的变量在while测试条件下不可见,这使得do { ... } while (...)的用处更少。等等。

解决方法:

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

无论价值多少。

作为另一种解释,也许马丁奥德斯基不得不面对一些因这种用法而产生的非常丑陋的错误,并决定将其从他的语言中取缔。

修改

David Pollackanswered有一些实际的事实,Martin Odersky他自己评论了他的答案这一事实清楚地赞同这一事实,并且相信Pollack提出的与绩效相关的问题论点

答案 2 :(得分:10)

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

class MyBean {
  private var internalState: String = _

  def state = internalState

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

state_=方法返回Unit(正如设置者所期望的那样),因为赋值返回Unit

我同意对于复制流或类似的C风格模式,这个特定的设计决定可能有点麻烦。然而,它实际上通常相对没有问题,并且真正有助于类型系统的整体一致性。

答案 3 :(得分:6)

也许这是由command-query separation原则引起的?

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改变列表,它们必须返回单元类型,从而阻止我们在我们不应该使用第二种形式时创建错误吨。因此,副作用的存在也会隐含在方法签名中。

答案 4 :(得分:4)

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

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

答案 5 :(得分:4)

将赋值用作布尔表达式不是最好的样式。您同时执行两项操作,这通常会导致错误。使用Scalas限制可以避免使用“=”代替“==”。

答案 6 :(得分:2)

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

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

当然,赋值出现了两次,但至少bytesRead属于它所属的范围,而且我没有玩搞笑的赋值技巧......

答案 7 :(得分: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进行检查,而无需输入。