Какова мотивация для присваивания Scala для оценки Unit, а не для присвоенного значения?
Какова мотивация для присваивания Scala для вычисления Unit, а не для назначенного значения?
Общим шаблоном в программировании ввода-вывода является следующее:
while ((bytesRead = in.read(buffer)) != -1) { ...
Но это невозможно в Scala, потому что...
bytesRead = in.read(buffer)
.. возвращает Unit, а не новое значение bytesRead.
Кажется, что интересная вещь - оставить функциональный язык.
Мне интересно, почему это было сделано?
Ответы
Ответ 1
Я выступал за то, чтобы присвоения возвращали назначенное значение, а не единицу. Мы с Мартином пошли туда и обратно, но его аргумент заключался в том, что добавление стоимости в стек просто для того, чтобы всплывать, 95% времени было пустой тратой байтовых кодов и отрицательно влияло на производительность.
Ответ 2
Я не привязан к внутренней информации по фактическим причинам, но мое подозрение очень просто. Scala делает побочные эффекты неудобными, чтобы программисты, естественно, предпочли бы для понимания.
Он делает это разными способами. Например, у вас нет цикла for
, где вы объявляете и изменяете переменную. Вы не можете (легко) мутировать состояние в цикле while
в то же время, когда вы проверяете условие, а это означает, что вам часто приходится повторять мутацию непосредственно перед ней и в конце ее. Переменные, объявленные внутри блока while
, не видны из условия тестирования while
, что делает do { ... } while (...)
гораздо менее полезным. И так далее.
Обход проблемы:
while ({bytesRead = in.read(buffer); bytesRead != -1}) { ...
Ибо что бы это ни стоило.
В качестве альтернативного объяснения, возможно, Мартину Одерскому пришлось столкнуться с несколькими очень уродливыми ошибками, вытекающими из такого использования, и решил запретить его с его языка.
ИЗМЕНИТЬ
Дэвид Поллак ответил на с некоторыми фактическими фактами, которые четко подтверждаются тем фактом, что Мартин Одерски сам прокомментировал свой ответ, отдавая должное аргументам, связанным с производительностью, выдвинутым Поллаком.
Ответ 3
Это произошло как часть Scala, имеющего более "формально правильную" систему типов. Формально говоря, присваивание является чисто побочным эффектом и поэтому должно возвращать Unit
. Это имеет некоторые приятные последствия; например:
class MyBean {
private var internalState: String = _
def state = internalState
def state_=(state: String) = internalState = state
}
Метод state_=
возвращает Unit
(как и ожидалось для установщика) именно потому, что присваивание возвращает Unit
.
Я согласен, что для шаблонов в стиле C, таких как копирование потока или аналогичного, это конкретное дизайнерское решение может быть немного неприятным. Однако это на самом деле относительно непроблематично в целом и действительно способствует общей согласованности системы типов.
Ответ 4
Возможно, это связано с принципом командной строки?
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()
В то время как в более функциональном стиле, скорее всего, будет написано что-то вроде этого:
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
изменяют список, они должны возвращать тип единицы измерения, что препятствует нам создавать ошибки, используя вторую форму, если мы не должны. Поэтому наличие побочных эффектов также становится неявным в сигнатуре метода.
Ответ 5
Я бы предположил, что это необходимо для того, чтобы программа/язык не имели побочных эффектов.
То, что вы описываете, - это намеренное использование побочного эффекта, который в общем случае считается плохим.
Ответ 6
Это не лучший стиль для использования назначения как булевского выражения. Вы одновременно выполняете две вещи, которые часто приводят к ошибкам. При использовании ограничения Scalas исключается использование "=" вместо "==".
Ответ 7
Кстати, я нахожу, что начальный трюк глупый, даже в Java. Почему это не так похоже?
for(int bytesRead = in.read(buffer); bytesRead != -1; bytesRead = in.read(buffer)) {
//do something
}
Конечно, назначение появляется дважды, но по крайней мере bytesRead находится в области, к которой он принадлежит, и я не играю с забавными трюками назначения...
Ответ 8
У вас может быть обходное решение для этого, если у вас есть ссылочный тип для косвенности. В наивной реализации вы можете использовать следующие для произвольных типов.
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
более неявным образом, не набирая его.