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