Чому незмінний набір Scala не є коваріантним за своїм типом?


94

РЕДАГУВАТИ : Переписати це питання на основі оригінальної відповіді

scala.collection.immutable.SetКласу не коваріантен в параметрі типу. Чому це?

import scala.collection.immutable._

def foo(s: Set[CharSequence]): Unit = {
    println(s)
}

def bar(): Unit = {
   val s: Set[String] = Set("Hello", "World");
   foo(s); //DOES NOT COMPILE, regardless of whether type is declared 
           //explicitly in the val s declaration
}

Варто зазначити, що foo(s.toSet[CharSequence])компілюється чудово. toSetМетод є O (1) - це просто обгортання asInstanceOf.
Джон Салліван

1
Зауважте також, що foo(Set("Hello", "World"))компіляція також проводиться 2.10, оскільки, схоже, Scala може зробити висновок про правильний тип Set. Однак це не працює з неявними перетвореннями ( stackoverflow.com/questions/23274033/… ).
LP_

Відповіді:


55

Setє інваріантним за своїм типовим параметром через концепцію множин як функцій. Наступні підписи повинні трохи прояснити ситуацію:

trait Set[A] extends (A=>Boolean) {
  def apply(e: A): Boolean
}

Якби Setковаріантні були A, applyметод не міг би взяти параметр типу Aчерез противаріантність функцій. Setпотенційно може бути контраваріантен в A, але це теж викликає питання , коли ви хочете зробити що - щось на зразок цього:

def elements: Iterable[A]

Коротше кажучи, найкращим рішенням є збереження незмінності навіть для незмінної структури даних. Ви помітите, що immutable.Mapвін також інваріантний за одним із параметрів свого типу.


4
Я думаю, цей аргумент залежать від "концепції множин як функцій" - чи можна це розширити? Наприклад, які переваги дає мені «набір як функція», а «набір як набір» ні? Чи варто втрачати використання цього коваріантного типу?
oxbow_lakes

23
Підпис типу - досить слабкий приклад. "Застосувати" набору - це те саме, що містить метод. На жаль, список Scala є спільним варіантом і також має метод містить. Звичайно, підпис для List's відрізняється, але метод працює так само, як Set. Отже, насправді ніщо не заважає Set бути спів-варіантом, крім дизайнерського рішення.
Даніель К. Собрал,

6
Набори не є логічними функціями з математичної точки зору. Набори "будуються" з аксіом Цермело-Фраенкеля, не зменшених якоюсь функцією включення. Причиною цього є парадокс Рассела: якщо щось може бути членом множини, то розглянемо множину R множин, які не є членами їх самих. Тоді задайте питання, чи є R членом R?
oxbow_lakes

12
Я все ще не впевнений, що жертвувати коваріантністю варто для Сет. Звичайно, приємно, що це предикат, але зазвичай ви можете бути просто більш багатослівним і використовувати "set.contains", а не "set" (і, безперечно, "set.contains" у багатьох випадках читається краще).
Matt R

4
@Martin: Оскільки метод List містить метод приймає Any, а не A. Тип List(1,2,3).contains _є (Any) => Boolean, тоді як тип Set(1,2,3).contains _є res1: (Int) => Boolean.
Seth Tisue

52

за адресою http://www.scala-lang.org/node/9764 Мартін Одерський пише:

"Що стосується питання наборів, я вважаю, що нерівномірність також випливає з реалізацій. Загальні набори реалізовані як хеш-теги, які є неваріантними масивами типу ключа. Я згоден, що це трохи дратує неправильність".

Отож, здається, всі наші зусилля з побудови принципової причини для цього були помилковими :-)


1
Але деякі послідовності також реалізовані з масивами, і все ще Seqє коваріантною ... я чогось пропускаю?
LP_

4
Це тривіально можна вирішити шляхом Array[Any]внутрішнього зберігання .
праворуч

@rightfold - це правильно. Можливо, є розумна причина, але це не так.
Пол Дрейпер

6

РЕДАГУВАТИ : для тих, хто цікавиться, чому ця відповідь здається дещо нетиповою, це тому, що я (запитувач) змінив питання.

Висновок типу Scala досить хороший, щоб зрозуміти, що вам потрібні CharSequences, а не Strings в деяких ситуаціях. Зокрема, для мене в 2.7.3 працює наступне:

import scala.collections.immutable._
def findCharSequences(): Set[CharSequence] = Set("Hello", "World")

Щодо того, як створити незмінний.HashSets безпосередньо: не. Як оптимізація реалізації, незмінні.Набори менше 5 елементів насправді не є екземплярами незмінюваних.Набори. Вони є або EmptySet, Set1, Set2, Set3 або Set4. Ці класи підкласу незмінні. Set, але не незмінні. HashSet.


Ти правий; намагаючись спростити свій фактичний приклад, я зробив тривіальну помилку :-(
oxbow_lakes
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.