Какова мотивация для присваивания 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 более неявным образом, не набирая его.