Scala赋值给Unit的动机是什么,而不是赋值?
Scala赋值给Unit的动机是什么,而不是赋值?
I / O编程的一个常见模式是做这样的事情:
while ((bytesRead = in.read(buffer)) != -1) { ...
但是在Scala中这是不可能的,因为…
bytesRead = in.read(buffer)
..返回Unit,不是bytesRead的新值。
看起来像是一个有趣的事情,离开了function语言。 我想知道为什么这样做?
我主张让任务返回分配的价值而不是单位。 马丁和我之间来回转移,但是他的观点是,为了在95%的时间内突然出现这个价值,浪费了字节码,并对性能产生了负面影响。
我并不了解内幕消息的真实原因,但是我的怀疑很简单。 斯卡拉使副作用循环尴尬,以便程序员自然喜欢理解。
它在很多方面都是这样做的。 例如,你没有一个for
循环声明和变异variables。 在testing条件的while
你不能(容易)在while
循环中改变状态,这意味着你经常必须在它之前和之后重复这个变异。 在while
块中声明的variables在while
testing条件下是不可见的,这使得do { ... } while (...)
更加有用。 等等。
解决方法:
while ({bytesRead = in.read(buffer); bytesRead != -1}) { ...
无论什么是值得的。
作为另一种解释,也许马丁·奥德斯基(Martin Odersky)不得不面对一些由于这种使用而产生的非常丑陋的错误,并决定将其从语言中取缔。
编辑
大卫·波拉克已经回答了一些实际的事实, 奥德斯基本人评论他的回答,并明确支持波拉克提出的与performance有关的问题的论点。
这是Scala的一部分,它具有更“正式”的types系统。 从forms上讲,转让是一个纯粹的副作用,因此应该返回Unit
。 这确实有一些不错的结果。 例如:
class MyBean { private var internalState: String = _ def state = internalState def state_=(state: String) = internalState = state }
state_=
方法返回Unit
(就像setter所期望的那样),因为赋值返回Unit
。
我同意,对于复制stream或类似的C风格模式,这个特定的devise决定可能有点麻烦。 然而,它实际上是相对没有问题的,并且真正有助于types系统的整体一致性。
我想这是为了保持程序/语言免费的副作用。
你所描述的是故意使用一般情况下被认为是不好的副作用。
也许这是由于命令 – 查询分离原理?
CQS在OO和函数式编程风格的交集中趋于stream行,因为它在有或没有副作用(即改变对象)的对象方法之间产生明显的区别。 将CQS应用于variables赋值比平常更进一步,但同样的想法也适用。
一个为什么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()
而更多的function风格,更可能写这样的事情:
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
改变列表,他们必须返回单元types,从而防止我们通过使用第二种forms创build错误。 因此副作用的存在也在方法签名中变得隐含。
使用赋值作为布尔expression式不是最好的风格。 你同时执行两件事情,经常导致错误。 用Scalas限制可以避免使用“=”而不是“==”。
顺便说一下:我发现了最初的愚蠢,即使在Java中。 为什么不像这样的事情?
for(int bytesRead = in.read(buffer); bytesRead != -1; bytesRead = in.read(buffer)) { //do something }
当然,这个任务会出现两次,但是至lessbytesRead属于它所属的范围,而且我不是在玩有趣的任务技巧。
只要你有间接引用types,你可以有一个解决方法。 在一个天真的实现中,可以使用以下任意types。
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
而不必键入它。