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 A
vì A
là biến thể cùng với kiểu trả về cho apply
. Tuy nhiên, contains
khô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 String
và Int
(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 String
và Int
, thì loại của nó là nên là Seq[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ố Int
và A
, 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 contains
xuấ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.
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. –
Đó 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'. –
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). –