Изменяемый набор += возвращаемое значение в Scala

Насколько я понимаю, смысл использования метода += для изменяемых наборов заключается в том, что

val s = collection.mutable.Set(1)
s += 2  // s is now a mutable Set(1, 2)

имеет аналогичный эффект

var s = Set(1) // immutable
s += 2  // s is now an immutable Set(1, 2)

Если это так, то почему метод += в изменяемом наборе возвращает сам набор? Разве это не усложнит рефакторинг кода, например.

val s = collection.mutable.Set(1)
val s1 = s += 2  // s and s1 are now mutable Set(1, 2)

не может быть преобразован в

var s = Set(1) // immutable
var s1 = s += 2  // s is immutable Set(1, 2), s1 is now ()

при сохранении первоначального значения. В чем причина такого дизайнерского решения?


person joel    schedule 24.08.2018    source источник


Ответы (2)


(Это, очевидно, просто предположение)

Коллекции Mutable Scala предназначались для использования в качестве Builders для неизменяемых коллекций. В JVM есть одна очень известная и очень древняя неизменяемая структура данных: String. Соответствующий компоновщик (никак не привязанный к Scala) уже существовал в Java: это был StringBuilder. Если вы заглянете в документацию, то увидите десятки версий перегруженного метода append. Каждый раз он возвращает сам StringBuilder, что позволяет написать следующее:

// java code
myBuilder
  .append('h')
  .append('i');

Я предполагаю, что Scala collection.mutable API просто имитировал поведение Java StringBuilder, но заменил append(...) несколько более коротким +=. В конце концов, это всего лишь реализация классического шаблона компоновщика.

person Andrey Tyukin    schedule 24.08.2018
comment
К сожалению, s += 2 += 3 += 4 += 5 вызывает ошибку компилятора для неизменяемых наборов, снова нарушая эквивалентность - person joel; 24.08.2018
comment
@JoelBerkeley Это не имеет большого значения. Изменяемые и неизменяемые коллекции — это совершенно разные звери. Неизменяемые коллекции — это подобные данным сообщения, которые могут передаваться между потоками и машинами по сети. Изменяемые коллекции — это подобные состояниям монады близкие к металлу штуковины, которые моделируют изменяемую память одного вычислительного узла для некоторых эффективных алгоритмов, которые работают быстрее с мутацией. Наборы сценариев, в которых могут применяться эти два вида коллекций, существенно не пересекаются. - person Andrey Tyukin; 24.08.2018
comment
@JoelBerkeley Хорошо, может быть, не несовместно - есть довольно много алгоритмов, которые могут быть реализованы в обоих стилях или даже требуют их сочетания. Но, тем не менее, эти семейства структур данных достаточно различны, и не имеет большого значения, если у них совершенно разные интерфейсы. - person Andrey Tyukin; 24.08.2018
comment
ХОРОШО. Я пытаюсь разработать основу для 1/2 страницы в Programming in Scala. Выбор имен методов += и -= означает, что очень похожий код может работать как с изменяемым, так и с неизменяемым наборы. - person joel; 24.08.2018
comment
@JoelBerkeley Очень похоже - что очень похоже? Это работает аналогично в случае lhs += singleElement. Это считается достаточно похожим? Определенно не похоже, что библиотека изменяемых коллекций была унаследована от C, тогда как библиотека неизменяемых коллекций была унаследована от Haskell. Эти две части API довольно похожи, осмелюсь сказать... - person Andrey Tyukin; 24.08.2018
comment
Да, это тот случай, который они имеют в виду. Мне было любопытно, почему они не собирались доводить его до конца - какие другие факторы имели приоритет, и вот тут-то и появляется ваш ответ. - person joel; 24.08.2018
comment
Неизменяемая версия приводит к Unit, потому что v">назначение возвращает Unit. Это ограничение №1. Выше я попытался набросать, почему может иметь смысл заставить += возвращать саму коллекцию в изменяемом случае. Это ограничение Nr2. Две части библиотеки должны вести себя одинаково. Это ограничение Nr3. Похоже, у вас может быть только 2 из 3-ситуаций. - person Andrey Tyukin; 24.08.2018
comment
@JoelBerkeley Поставьте лайк здесь, но при этом 1) = должен возвращать Unit, 2) += должен возвращать саму коллекцию, 3) mutable/immutable должны быть максимально похожи. Они выбрали первых двух. - person Andrey Tyukin; 24.08.2018
comment
@AndreyTyukin должен вернуть Unit? должен? Я так не думаю. Я просмотрел ссылку, которую вы предложили, чтобы обосновать, что присваивания возвращают единицы, но это кажется таким же действительным, как, например, запрет функций с одним оператором (зачем помещать что-то в стек, что вы могли бы просто использовать встроенный?). Оптимизатора можно было бы тривиально научить не помещать значение в стек, если оно не будет выбрано, я не думаю, что это имеет какое-либо отношение к фактической семантике языка. - person Dima; 25.08.2018
comment
@Dima Насколько я понял, если бы присваивание возвращало присвоенное значение, то каждый сеттер def setFoo(newFoo: Foo) = { privateFoo = newFoo } должен был бы быть переписан с ; Unit } в конце, чтобы возвращалось Unit вместо newFoo. В качестве альтернативы можно позволить установщикам возвращать newFoo, но затем попытаться оптимизировать его позже. В этом комментарий Одерский утверждал, что это было бы больно. Избегание боли звучит как веская причина - person Andrey Tyukin; 25.08.2018
comment
@AndreyTyukin Я думаю, вполне разумно утверждать, что написание такого сложного и продвинутого компилятора, как scala, само по себе является проблемой. Почему бы всем просто не придерживаться C? :) В общем, я просто не вижу, как это вообще может быть проблемой. Нажатие/извлечение одного значения в стек не это дорого, и мутации достаточно редки в функциональном языке для начала. Я имею в виду, мы готовы жить с тем, что :+ является O(n), и с .filterNot(_ == null), делающим копию всей коллекции, но не с добавлением дополнительного значения в стек? Да ладно! :D - person Dima; 25.08.2018
comment
Кроме того, вам не нужны ; Unit для сеттеров. Просто приписывание типа подойдет, если вы действительно так обеспокоены этой проблемой ... Приписывание типов настоятельно рекомендуется для общедоступных методов, и я просто не понимаю, насколько оптимизация push будет проблемой для частных.. , кажется довольно тривиальным. - person Dima; 25.08.2018
comment
@Dima Вы говорите, что мутации достаточно редки, связанный ответ говорит, что возвращаемое значение отбрасывается в 95% случаев, поэтому мы говорим о распространенности 5% * rareEnough. И даже в этих сверхредких while((line = read()) != null) случаях тот факт, что присваивание возвращает Unit, почти не вредит, потому что это можно исправить 1-2 дополнительными строками кода... Так что, я бы сказал, что это в основном сбрасывание велосипеда, и на самом деле у меня даже нет твердого мнения о типе возврата = ;-P. Может быть, OCaml let x = ref 0;; x := 1;;, возвращающий - : unit = (), послужил источником вдохновения? - person Andrey Tyukin; 25.08.2018
comment
Нет, речь идет не о распространенности 5%*rareEnough, а о стоимости 95%*veryCheap*rareEnough :). Конечно, это можно исправить с помощью нескольких строк кода... почти все может. Мы могли бы просто написать C. На нем можно написать все, и работать он будет намного быстрее... Это также потребует дополнительного кода, но не более чем несколько дополнительных строк тут и там :) - person Dima; 25.08.2018
comment
@Dima Нет. Не все можно написать на C. Последние 20-30 лет или около того, по крайней мере, в области статически типизированных языков, речь идет не о том, что программы могут делать во время выполнения, а о том, что программист может доказать их правильность во время компиляции. Если язык может делать все, что может делать машина Тьюринга (с stdI/O), но не позволяет мне кодировать сложные ограничения когерентности (например, в форме типов), то мне просто все равно. C в этом отношении далеко не так выразителен, как Scala. Этот аргумент Turing-tarpit больше не должен вызываться, он должен быть объявлен устаревшим. - person Andrey Tyukin; 25.08.2018
comment
@Dima Исправление while ((line = read()) != null) дополнительным var требует O(1) дополнительных строк кода (написано и поддерживается в O(1) файлах O(1) программистами). Это незначительно. С другой стороны, написание библиотеки, обеспечивающей типобезопасные неизменяемые List в C, — это не просто O(exp(N)), O(gamma(N)) или O(anythingHorrible(N)) накладных расходов — это совершенно невозможно без написания счетно-бесконечного числа ListTypeNumberN-классов, тогда как в Scala это можно сделать тривиально в конечное количество строк. - person Andrey Tyukin; 25.08.2018
comment
@Dima ... Поэтому я бы предпочел беспокоиться о вещах подобных этому, которые делают всю систему типов несколько нарушенной. Небольшие синтаксические неприятности кажутся не такими уж важными, поскольку система типов содержит странные конструкции и пограничные случаи, которые делают ее несостоятельной... И, кстати: я не в первый раз замечаю, что нам обоим нравится иметь последнее слово в длинных обсуждениях комментариев. Так что я думаю, мне лучше остановиться сейчас, согласившись с тем, что есть веские аргументы для =, возвращающего присвоенное значение :) - person Andrey Tyukin; 25.08.2018
comment
@AndreyTyukin Нет, кому нужен безопасный неизменяемый список типов, это намного дороже, чем время от времени помещать дополнительное значение в стек. :) Все, что вам нужно, это дополнительный код O(1) для malloc и free... - person Dima; 25.08.2018

В неизменном случае s += 2 совпадает с s = s + 2. Итак, если вы заставляете s += 2 оценивать новое значение s, то вам как бы нужно заставить каждый оператор присваивания оценивать результат присваивания. Другие языки делают это таким образом, но исторически это приводило к ошибкам, например, с кодом C, например:

if (x = 0) {
  ...
}

Поэтому я думаю, что имеет смысл не возвращать набор.

С другой стороны, для изменчивого случая += — это просто имя метода, поэтому он не выполняет присваивание и не может быть ответственным за такого рода ошибки осмысленным образом. А то, что он сам возвращается, позволяет использовать цепочку шаблонов построителя, которая иногда полезна.

person Joe K    schedule 24.08.2018
comment
if (x = 0) { .. } является проблемой, потому что int каким-то образом совместимо с boolean, а не потому, что присваивание оценивает свой результат. Последнее, я думаю, является совершенно полезной функцией. - person Dima; 25.08.2018