Какова мотивация присвоения Scala оценивать Unit, а не присвоенное значение?
Обычный шаблон в программировании ввода-вывода - это делать что-то вроде этого:
while ((bytesRead = in.read(buffer)) != -1) { ...
Но в Scala это невозможно, потому что ...
bytesRead = in.read(buffer)
.. возвращает Unit, а не новое значение bytesRead.
Похоже, что интересно не использовать функциональный язык. Интересно, зачем это было сделано?
Ответы:
Я выступал за то, чтобы присваивания возвращали присвоенное значение, а не единицу. Мы с Мартином ходили по этому поводу, но его аргумент состоял в том, что размещение значения в стеке только для того, чтобы извлечь его в 95% случаев, было пустой тратой байтовых кодов и отрицательно сказывалось на производительности.
источник
void
. В Scalafoo_=(v: Foo)
должен возвращаться,Foo
если присваивание выполняется.void
(Unit
), назначенияx = value
переводятся в эквивалентx.set(value);x.get(value)
; компилятор исключает на этапах оптимизацииget
вызовы -вызовы, если значение не использовалось. Это могло быть долгожданным изменением в новом крупном (из-за обратной несовместимости) версии Scala и меньшим раздражением для пользователей. Что вы думаете?Я не владею внутренней информацией об истинных причинах, но мои подозрения очень просты. Scala делает неудобным использование побочных циклов, поэтому программисты, естественно, предпочтут for-computing.
Делается это разными способами. Например, у вас нет
for
цикла, в котором вы объявляете и изменяете переменную. Вы не можете (легко) изменить состояниеwhile
цикла одновременно с проверкой условия, а это значит, что вам часто приходится повторять мутацию непосредственно перед ним и в конце. Переменные, объявленные внутриwhile
блока, не видны изwhile
условия теста, что делаетdo { ... } while (...)
гораздо менее полезными. И так далее.Обходной путь:
while ({bytesRead = in.read(buffer); bytesRead != -1}) { ...
Во что бы то ни стало.
В качестве альтернативного объяснения, возможно, Мартину Одерски пришлось столкнуться с несколькими очень уродливыми ошибками, возникающими из-за такого использования, и он решил запретить это в своем языке.
РЕДАКТИРОВАТЬ
Дэвид Поллак уже ответил с некоторыми реальными фактами, которые четко подтверждено тем фактом , что Одерски сам прокомментировал свой ответ, давая правдоподобность аргументу вопросы , связанные с производительностью , выдвинутой Поллак.
источник
for
версия цикла была бы такой: иfor (bytesRead <- in.read(buffer) if (bytesRead) != -1
это здорово, за исключением того, что она не будет работать, потому что ее нетforeach
иwithFilter
нет!Это произошло как часть того, что Scala имела более «формально правильную» систему типов. Формально говоря, присваивание - это чисто побочный оператор, и поэтому он должен возвращаться
Unit
. Это имеет приятные последствия; например:class MyBean { private var internalState: String = _ def state = internalState def state_=(state: String) = internalState = state }
В
state_=
методе возвращает значениеUnit
(как можно было бы ожидать для инкубатора) именно потому , что возвращает назначениеUnit
.Я согласен с тем, что для шаблонов в стиле C, таких как копирование потока или аналогичные, это конкретное дизайнерское решение может быть немного хлопотным. Однако на самом деле это относительно несложно и действительно способствует общей согласованности системы типов.
источник
Возможно, это связано с разделением команд и запросов принципом ?
CQS имеет тенденцию быть популярным на пересечении объектно-ориентированного и функционального стилей программирования, поскольку он создает очевидное различие между объектными методами, которые имеют или не имеют побочных эффектов (т. Е. Изменяют объект). Применение CQS к присвоению переменных идет дальше обычного, но применима та же идея.
Короткая иллюстрация того , почему ОКК полезно: Рассмотрим гипотетический гибридный язык F / OO с
List
классом , который имеет методыSort
,Append
,First
, иLength
. В императивном стиле объектно-ориентированного программирования можно написать такую функцию: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
изменить список, они должны возвращать тип единицы, таким образом предотвращая нас от создания ошибок, используя вторую форму , когда мы не должны. Таким образом, наличие побочных эффектов также становится неявным в сигнатуре метода.источник
Я предполагаю, что это сделано для того, чтобы программа / язык не имели побочных эффектов.
Вы описываете намеренное использование побочного эффекта, который в общем случае считается плохим.
источник
val a = b = 1
(представьте себе «волшебный»val
передb
) противval a = 1; val b = 1;
.Использование присваивания как логического выражения - не лучший вариант. Вы выполняете две вещи одновременно, что часто приводит к ошибкам. А случайное использование «=» вместо «==» избегается с помощью ограничения Scalas.
источник
Между прочим: я считаю первоначальный трюк с while глупым даже в Java. Почему не что-нибудь подобное?
for(int bytesRead = in.read(buffer); bytesRead != -1; bytesRead = in.read(buffer)) { //do something }
Конечно, назначение появляется дважды, но, по крайней мере, bytesRead находится в той области видимости, к которой оно принадлежит, и я не играю с забавными трюками с назначениями ...
источник
У вас может быть обходной путь, если у вас есть ссылочный тип для косвенного обращения. В наивной реализации для произвольных типов можно использовать следующее.
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
более неявным образом, не вводя его.источник