2010-01-16 26 views
13

Ví dụ:Tại sao Seq.contains chấp nhận kiểu Bất kỳ thay vì tham số kiểu A?

scala> val l:List[String] = List("one", "two") 
l: List[String] = List(one, two) 

scala> l.contains(1) //wish this didn't compile 
res11: Boolean = false 

The various explanations of why things were done this way in Java dường như không áp dụng càng nhiều ở đây, như Bản đồ và Set làm triển khai phiên bản kiểu an của contains và bạn bè. Có cách nào để làm một loại an toàn contains trên một Seq ngắn nhân bản nó thành một Set?

Trả lời

30

Vấn đề là Seq là biến thể trong thông số loại của nó. Điều này làm cho rất nhiều ý nghĩa cho phần lớn các chức năng của nó. Là một vùng chứa không thay đổi, nó thực sự là nên là biến thể. Thật không may, điều này không nhận được trong cách khi họ phải xác định một phương pháp mà có một số loại tham số. Hãy xem xét ví dụ sau:

trait Seq[+A] { 
    def apply(i: Int): A  // perfectly valid 

    def contains(v: A): Boolean // does not compile! 
} 

Vấn đề là các hàm luôn luôn thay đổi trong loại tham số và biến thể trong loại trả về của chúng. Do đó, phương thức apply có thể trả về giá trị loại AA là biến thể cùng với kiểu trả về cho apply. Tuy nhiên, containskhông thể có giá trị loại A vì thông số của nó phải là contravariant.

Sự cố này có thể được giải quyết theo nhiều cách khác nhau. Một tùy chọn là chỉ cần thực hiện A một tham số kiểu bất biến. Điều này cho phép nó được sử dụng trong cả hai bối cảnh covariant và contravariant. Tuy nhiên, thiết kế này có nghĩa là Seq[String] sẽ không là loại phụ của Seq[Any]. Một tùy chọn khác (và thường được sử dụng nhất) là sử dụng một tham số kiểu cục bộ được giới hạn bên dưới theo kiểu biến đổi. Ví dụ:

trait Seq[+A] { 
    def +[B >: A](v: B): Seq[B] 
} 

lừa này vẫn giữ được Seq[String] <: Seq[Any] bất động sản cũng như đưa ra một số kết quả rất trực quan khi viết mã trong đó sử dụng các thùng chứa không đồng nhất. Ví dụ:

val s: Seq[String] = ... 
s + 1  // will be of type Seq[Any] 

Kết quả của + hàm trong ví dụ này là một giá trị kiểu Seq[Any], vì Any là đơn giản nhất trên-bound (LUB) cho các loại StringInt (nói cách khác, ít nhất siêu phổ biến). Nếu bạn nghĩ về nó, đây chính xác là hành vi mà chúng tôi mong đợi.Nếu bạn tạo chuỗi có cả hai thành phần StringInt, thì loại của nó là nênSeq[Any].

Thật không may, thủ thuật này, trong khi áp dụng các phương pháp như contains, sản xuất một số kết quả đáng ngạc nhiên:

trait Seq[+A] { 
    def contains[B >: A](v: B): Boolean // compiles just fine 
} 

val s: Seq[String] = ... 
s contains 1  // compiles! 

Vấn đề ở đây là chúng ta đang kêu gọi các contains phương pháp đi qua một giá trị kiểu Int. Scala thấy điều này và cố gắng suy ra một loại cho B là siêu kiểu của cả hai số IntA, trong trường hợp này được khởi tạo là String. LUB cho hai loại này là Any (như được hiển thị trước đó) và do đó, kiểu instantiation cục bộ cho contains sẽ là Any => Boolean. Do đó, phương thức containsxuất hiện để không được nhập an toàn.

Kết quả này không phải là một vấn đề đối với Map hoặc Set vì không ai trong số họ được hiệp biến trong các loại tham số của họ:

trait Map[K, +V] { 
    def contains(key: K): Boolean // compiles 
} 

trait Set[A] { 
    def contains(v: A): Boolean  // also compiles 
} 

Vì vậy, câu chuyện dài ngắn, phương pháp contains trên các loại thùng chứa hiệp biến có thể không bị hạn chế để chỉ lấy các giá trị của kiểu thành phần vì cách mà các kiểu hàm hoạt động (contravariant trong các kiểu tham số của chúng). Điều này không thực sự là một hạn chế của Scala hoặc thực hiện xấu, đó là một thực tế toán học.

Giải khuyến khích là đây thực sự không phải là vấn đề trong thực tế. Và, như các câu trả lời khác đã đề cập, bạn luôn có thể xác định chuyển đổi tiềm ẩn của riêng bạn, thêm phương thức "loại an toàn" contains nếu bạn thực sự là cần kiểm tra thêm.

+0

Thật đáng tiếc là không có cơ chế để hướng dẫn trình biên dịch về các tham số chỉ chính thức ở đó để đáp ứng các yêu cầu về loại và có thể được thu hẹp trong một ngữ cảnh cụ thể. Đúng là danh sách [B] chỉ có thể chứa B, nhưng cũng đúng là Danh sách [B] có thể được diễn giải lại thành Danh sách [A] trong đó B <: A và sau đó có thể được hỏi (vô dụng) về A. –

+1

Đó chính xác là những gì đang xảy ra ở đây. 'Seq [String]' đang được hiểu lại là 'Seq [Any]', và từ 'Int <: Any', chúng ta có thể hỏi (vô nghĩa) về' Int'. –

+0

Tôi rất muốn xem toán học do đó điều này trở thành một thực tế toán học (ngoài hàm functor là contravariant). –

4

Tôi không chắc tại sao mọi thứ được thiết kế theo cách này - có thể để phản chiếu điều gì đó trong Java.

Dù sao, đó là hiệu quả hơn để sử dụng mô hình ma cô-my-thư viện hơn để sao chép vào một tập hợp:

class SeqWithHas[T](s: Seq[T]) { 
    def has(t: T) = s.contains(t) 
} 
implicit def seq2seqwithhas[T](s: Seq[T]) = new SeqWithHas(s) 

scala> List("3","5") has 1 
<console>:7: error: type mismatch; 
found : Int(1) 
required: java.lang.String 
     List("3","5") has 1 
         ^

scala> List("3","5") has "1" 
res1: Boolean = false 

(Bạn có lẽ sẽ muốn đặt công cụ này và tiện dụng điều đó khác vào một đơn đối tượng và sau đó nhập MyHandyObject._ vào hầu hết các tệp nguồn của bạn.)

3

Nếu bạn sẵn sàng bỏ qua ưu tiên cuộc gọi phương thức thông thường, việc xác định và nhập phương thức (...) sau đây sẽ tránh tạo một ví dụ mỗi khi bạn cần thử nghiệm "có" an toàn loại (đáng giá trong các vòng trong, ví dụ):

def has[T](s: Set[T], t: T) = s.contains(t) 

Đương nhiên, đặt [T] có thể được thư giãn theo loại ít nhất cụ thể có phương pháp chứa.

+0

lĩnh vực Nhận xét này là quá nhỏ để gửi mã, nhưng tối ưu hóa khá giỏi trong việc cho phép các phương pháp ghi vào gây ra chỉ một chút chậm lại: ngầm so với phương pháp (thử nghiệm 2M trên danh sách dài 3): 1247 so với 1141; 1215 so với 1076; 1184 so với 1091; 1181 so với 1087; 1180 so với 1085; 1183 so với 1095; 1200 so với 1103; 1203 so với 1093; 1183 và 1088; 1188 so với 1087. Không quá tệ - khoảng 10% chi phí cho việc tạo đối tượng ẩn. –

0

Bằng cách cung cấp một bằng chứng cho loại bình đẳng,

def contains[A,B](xs: List[A], x: B)(implicit ev: A =:= B) = xs.contains(x) 
Các vấn đề liên quan