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

Gra*_*Lea 82 io functional-programming scala assignment-operator

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

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

while ((bytesRead = in.read(buffer)) != -1) { ...
Run Code Online (Sandbox Code Playgroud)

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

bytesRead = in.read(buffer)
Run Code Online (Sandbox Code Playgroud)

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

从函数式语言中省略,似乎是一件有趣的事情.我想知道为什么这样做了?

Dav*_*lak 82

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

  • 在setter出现的情况下并不容易:每个setter都必须返回一个结果,这是一个很难写的.然后编译器必须优化它,这很难跨越调用. (42认同)
  • 有没有理由为什么Scala编译器无法查看赋值的值是否实际使用,并相应地生成有效的字节码? (7认同)
  • @Martin Odersky:跟随怎么样:setters仍然是`void`(`Unit`),赋值`x = value`被翻译成等价的`x.set(value); x.get(value)`; 如果值未被使用,编译器会在优化阶段消除`get`-calls.它可能是一个新主要的一个值得欢迎的变化(因为向后不兼容)Scala的发布和对用户的刺激更少.你怎么看? (5认同)
  • @PhươngNguyễn区别在于统一访问原则.在C#/ Java setter(通常)返回`void`.在Scala`foo _ =(v:Foo)中``如果赋值,则应返回`Foo`. (3认同)

Dan*_*ral 20

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

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

解决方法:

while ({bytesRead = in.read(buffer); bytesRead != -1}) { ... 
Run Code Online (Sandbox Code Playgroud)

无论它值多少钱.

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

编辑

大卫·波拉克已经回答了一些实际的事实,这显然的事实,同意马丁·奥德斯基自己评价他的回答,让可信的性能相关的问题争论由波拉克提出.

  • 所以推测`for`循环版本可能是:`for(bytesRead < - in.read(buffer)if(bytesRead)!= -1`这很好,除非它不起作用,因为没有`foreach`和` withFilter`可用! (3认同)

Dan*_*wak 11

这是因为Scala的一部分具有更"正式正确"的类型系统.从形式上讲,转让是纯粹的副作用声明,因此应该返回Unit.这确实有一些很好的结果; 例如:

class MyBean {
  private var internalState: String = _

  def state = internalState

  def state_=(state: String) = internalState = state
}
Run Code Online (Sandbox Code Playgroud)

state_=方法返回Unit(正如预期的那样),因为赋值返回Unit.

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

  • @Graham:但是,你必须遵循一致性,并确保你所有的设置者无论多么复杂,他们都会返回他们设定的价值.在某些情况下,这会很复杂,而在其他情况下,我认为这是错误的.(如果出现错误,你会返回什么?null? - 而不是.没有? - 那么你的类型将是Option [T].)我认为很难与之保持一致. (2认同)

C. *_*ann 7

也许这是由于命令查询分离原则造成的?

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()
Run Code Online (Sandbox Code Playgroud)

而在功能更强大的风格中,人们更有可能写出这样的东西:

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()
Run Code Online (Sandbox Code Playgroud)

这些似乎试图做同样的事情,但显然其中一个是不正确的,并且在不知道更多关于方法的行为的情况下,我们无法分辨哪一个.

但是,使用CQS,我们坚持认为如果AppendSort改变列表,它们必须返回单元类型,从而阻止我们在不应该使用第二种形式时创建错误.因此,副作用的存在也变得隐含在方法签名中.