2013-03-08 25 views
6

Tôi đang triển khai cấu trúc dữ liệu. Mặc dù nó không kết hợp trực tiếp với bất kỳ đặc điểm thu thập tiêu chuẩn nào của Scala, nhưng tôi muốn đưa vào phương thức to[Col[_]], với một nhà máy xây dựng, có thể tạo ra các bộ sưu tập Scala tiêu chuẩn.Thêm phương thức `vào [Col [_]]` cho tập hợp biến đổi

Bây giờ giả sử này, sao chép từ GenTraversableOnce:

trait Foo[+A] { 
    def to[Col[_]](implicit cbf: CanBuildFrom[Nothing, A, Col[A]]): Col[A] 
} 

này không thành công với error: covariant type A occurs in invariant position.

Vậy làm thế nào để GenTraversableOnce đạt được điều này? Tôi có thể thấy trong mã nguồn, rằng họ thêm một số annotation.unchecked.uncheckedVariance ...

Điều đó giống như một mẹo bẩn. Nếu typer từ chối điều này bình thường, làm thế nào điều này có thể được an toàn và tắt với uncheckedVariance?

Trả lời

2

Kiểm tra phương sai là một phần rất quan trọng trong việc kiểm tra kiểu và bỏ qua nó có thể dễ dàng gây ra lỗi kiểu thời gian chạy. Ở đây tôi có thể chứng minh một loại dân cư với một giá trị thời gian chạy không hợp lệ bằng cách in nó. Tôi đã không thể làm cho nó sụp đổ với một loại trừ ngoại lệ mặc dù.

import collection.generic.CanBuildFrom 
import collection.mutable.Builder 
import scala.annotation.unchecked.uncheckedVariance 

trait Foo[+A] { 
    def toCol[Col[_]](implicit cbf: CanBuildFrom[Nothing, A, Col[A @uncheckedVariance]]): Col[A @uncheckedVariance] 
} 

object NoStrings extends Foo[String] { 
    override def toCol[Col[_]](implicit cbf: CanBuildFrom[Nothing, String, Col[String]]): Col[String] = { 
    val res : Col[String] = cbf().result 
    println("Printing a Col[String]: ") 
    println(res) 
    res 
    } 
} 

case class ExactlyOne[T](t : T) 

implicit def buildExactlyOne = new CanBuildFrom[Nothing, Any, ExactlyOne[Any]] { 
    def apply() = new Builder[Any, ExactlyOne[Any]] { 
    def result = ExactlyOne({}) 
    def clear = {} 
    def +=(x : Any) = this 
    } 
    def apply(n : Nothing) = n 
} 

val noStrings : Foo[Any] = NoStrings 
noStrings.toCol[ExactlyOne] 

Đây println(res) với res : Col[String] in ExactlyOne(()). Tuy nhiên, ExactlyOne(()) không có loại Col[String], thể hiện lỗi loại.

Để giải quyết vấn đề trong khi tôn trọng quy tắc sai chúng ta có thể di chuyển mã bất biến ra khỏi đặc điểm và chỉ giữ lại một phần hiệp biến, trong khi sử dụng chuyển đổi ngầm để chuyển đổi từ đặc điểm hiệp biến đến lớp helper bất biến:

import collection.generic.CanBuildFrom 

trait Foo[+A] { 
    def to[R](implicit cbf: CanBuildFrom[Nothing, A, R]): R 
} 

class EnrichedFoo[A](foo : Foo[A]) { 
    def toCol[Col[_]](implicit cbf: CanBuildFrom[Nothing, A, Col[A]]): Col[A] = 
    foo.to[Col[A]] 
} 

implicit def enrich[A](foo : Foo[A]) = new EnrichedFoo(foo) 

case class Bar[A](elem: A) extends Foo[A] { 
    def to[R](implicit cbf: CanBuildFrom[Nothing, A, R]): R = { 
    val b = cbf() 
    b += elem 
    b.result() 
    } 
} 

val bar1 = Bar(3) 
println(bar1.toCol[Vector]) 
2

Có thể vì nó đang sử dụng chú thích @uncheckedVariance để khoanh vùng hệ thống kiểu và bỏ qua việc kiểm tra phương sai.

Đơn giản chỉ cần import scala.annotation.unchecked.uncheckedVariance và chú thích các loại mà bạn muốn kiểm tra phương sai khuyết tật:

def to[Col[_]](implicit cbf: CanBuildFrom[Nothing, A, Col[A @uncheckedVariance]]): Col[A @uncheckedVariance] 

Xem giải thích đầy đủ hơn in the related answer.

2

Tôi đã đọc liên kết đến câu hỏi khác được đề cập bởi @ axel22. Tuy nhiên, nó vẫn không phải là lý do thực tế (cho phép GenTraversableOnce hoạt động cho cả bộ sưu tập biến thể và bất biến — nó covariant trong A).

Ví dụ, sau đây làm việc một cách chính xác mà không cần ép buộc các typer:

import collection.generic.CanBuildFrom 

trait Foo[+A] { 
    def to[A1 >: A, Col[_]](implicit cbf: CanBuildFrom[Nothing, A1, Col[A1]]): Col[A1] 
} 

case class Bar[A](elem: A) extends Foo[A] { 
    def to[A1 >: A, Col[_]](implicit cbf: CanBuildFrom[Nothing, A1, Col[A1]]): Col[A1]= { 
    val b = cbf() 
    b += elem 
    b.result() 
    } 
} 

này sẽ theo ý kiến ​​của tôi là chữ ký chính xác. Nhưng sau đó tất nhiên, nó được xấu xí:

val b = Bar(33) 
b.to[Int, Vector] 

Vì vậy, giải thích của tôi về việc sử dụng các @uncheckedVariance chỉ đơn thuần là để tránh phải lặp lại các loại nguyên tố (như giới hạn trên) trong chữ ký to.

Điều đó vẫn không trả lời, tuy nhiên, nếu chúng ta có thể tưởng tượng một trường hợp dẫn đến lỗi thời gian chạy từ bỏ qua phương sai?

+1

Bạn don 't thực sự cần' A' và 'A1' liên quan ở tất cả. Điều này cũng hoạt động: 'def to [B, Col [_]] (ngầm định cbf: CanBuildFrom [Nothing, A, Col [B]]): Col [B]'. Ngay cả điều này có vẻ hạn chế không cần thiết ở chỗ nó chỉ cho phép kết quả của hình dạng 'Col [B]'. Điều này có vẻ chung chung và đơn giản hơn: 'def to [R] (ngầm định cbf: CanBuildFrom [Nothing, A, R]): R'. – Rotsor

Các vấn đề liên quan