Tại sao Bộ bất biến của Scala không đồng biến trong kiểu của nó?


94

CHỈNH SỬA : Đã viết lại câu hỏi này dựa trên câu trả lời ban đầu

Các scala.collection.immutable.Setlớp học không được hiệp biến trong tham số kiểu của nó. Tại sao thế này?

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
}

Cần lưu ý rằng foo(s.toSet[CharSequence])biên dịch tốt. Các toSetphương pháp là O (1) - nó chỉ kết thúc tốt đẹp asInstanceOf.
john sullivan 22/09/13

1
Cũng lưu ý rằng foo(Set("Hello", "World"))biên dịch quá trên 2.10, vì Scala dường như có thể suy ra đúng loại Tập hợp. Mặc dù vậy, nó không hoạt động với các chuyển đổi ngầm ( stackoverflow.com/questions/23274033/… ).
LP_

Câu trả lời:


55

Setlà bất biến trong tham số kiểu của nó vì khái niệm đằng sau các tập hợp là các hàm. Các chữ ký sau đây sẽ làm rõ mọi thứ một chút:

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

Nếu Setlà hiệp phương sai trong A, applyphương thức sẽ không thể nhận tham số kiểu Ado sự trái ngược của các hàm. Setkhả năng có thể được contravariant trong A, nhưng điều này cũng gây ra vấn đề khi bạn muốn làm những việc như thế này:

def elements: Iterable[A]

Nói tóm lại, giải pháp tốt nhất là giữ cho mọi thứ luôn bất biến, ngay cả đối với cấu trúc dữ liệu bất biến. Bạn sẽ nhận thấy điều đó immutable.Mapcũng bất biến ở một trong các tham số kiểu của nó.


4
Tôi đoán đối số này xoay quanh "khái niệm đằng sau tập hợp dưới dạng hàm" - điều này có thể được mở rộng không? Ví dụ, "tập hợp dưới dạng hàm" mang lại cho tôi những lợi thế nào mà "tập hợp dưới dạng tập hợp" không? Việc sử dụng loại hiệp phương sai đó có đáng không?
oxbow_lakes

23
Chữ ký kiểu là một ví dụ khá yếu. "Áp dụng" của một tập hợp giống như phương thức chứa của nó. Than ôi, Scala's List là đồng biến thể và cũng có phương thức chứa. Tất nhiên, chữ ký cho các hàm chứa của List là khác nhau, nhưng phương thức hoạt động giống như của Set. Vì vậy, không có gì thực sự ngăn Set trở thành đồng biến thể, ngoại trừ một quyết định thiết kế.
Daniel C. Sobral,

6
Bộ không phải là hàm boolean từ góc độ toán học. Các tập hợp được "xây dựng" từ các tiên đề Zermelo-Fraenkel không bị giảm bởi một số hàm bao hàm. Lý do đằng sau điều này là nghịch lý của Russell: nếu bất cứ thứ gì có thể là thành viên của một tập hợp, thì hãy xem xét Tập hợp R của các tập hợp không phải là thành viên của chính chúng. Sau đó đặt câu hỏi R có phải là thành viên của R không?
oxbow_lakes

12
Tôi vẫn không tin rằng việc hy sinh hiệp phương sai là xứng đáng đối với Set. Chắc chắn, thật tuyệt khi đó là một vị ngữ, nhưng bạn thường có thể dài dòng hơn một chút và sử dụng "set.contains" thay vì "set" (và dù sao thì "set.contains" đọc tốt hơn trong nhiều trường hợp).
Matt R ngày

4
@Martin: Vì phương thức chứa của List lấy Any chứ không phải A. Kiểu List(1,2,3).contains _(Any) => Boolean, trong khi kiểu Set(1,2,3).contains _res1: (Int) => Boolean.
Seth Tisue

52

tại http://www.scala-lang.org/node/9764 Martin Odersky viết:

"Về vấn đề tập hợp, tôi tin rằng sự không phương sai cũng bắt nguồn từ việc triển khai. Các tập hợp phổ biến được triển khai dưới dạng hashtable, là mảng không biến thể của loại khóa. Tôi đồng ý rằng đó là một sự bất thường hơi khó chịu."

Vì vậy, có vẻ như tất cả những nỗ lực của chúng tôi để xây dựng một lý do chính cho điều này đã sai lầm :-)


1
Nhưng một số trình tự cũng được triển khai với mảng và vẫn đồng Seqbiến ... tôi có thiếu thứ gì đó không?
LP_

4
Điều này có thể được giải quyết bằng cách lưu trữ Array[Any]nội bộ.
sang phải

@rightfold là đúng. Có thể có một lý do hợp lý, nhưng đây không phải là nó.
Paul Draper

6

CHỈNH SỬA : đối với bất kỳ ai thắc mắc tại sao câu trả lời này có vẻ hơi lạc đề, điều này là do tôi (người hỏi) đã sửa đổi câu hỏi.

Suy luận kiểu của Scala đủ tốt để tìm ra rằng bạn muốn CharSequences chứ không phải Strings trong một số tình huống. Đặc biệt, những điều sau đây phù hợp với tôi trong 2.7.3:

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

Về cách tạo trực tiếp Immutable.HashSets: không. Là một tối ưu hóa triển khai, Immutable.HashSet có ít hơn 5 phần tử thực sự không phải là trường hợp của Immutable.HashSet. Chúng là EmptySet, Set1, Set2, Set3 hoặc Set4. Các lớp này là lớp con Immutable.Set, nhưng không phải là không thể thay đổi.HashSet.


Bạn đúng rồi; trong cố gắng để đơn giản hóa ví dụ thực tế của tôi, tôi đã phạm sai lầm tầm thường :-(
oxbow_lakes
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.