Tôi nghĩ rằng loại tách rời lớp đầu tiên là một siêu kiểu kín, với các kiểu con thay thế và chuyển đổi ngầm định sang / từ các kiểu phân tách mong muốn sang các kiểu con thay thế này.
Tôi giả sử địa chỉ này nhận xét 33 - 36 về giải pháp của Miles Sabin, vì vậy loại lớp đầu tiên có thể được sử dụng tại trang web sử dụng, nhưng tôi đã không kiểm tra nó.
sealed trait IntOrString
case class IntOfIntOrString( v:Int ) extends IntOrString
case class StringOfIntOrString( v:String ) extends IntOrString
implicit def IntToIntOfIntOrString( v:Int ) = new IntOfIntOrString(v)
implicit def StringToStringOfIntOrString( v:String ) = new StringOfIntOrString(v)
object Int {
def unapply( t : IntOrString ) : Option[Int] = t match {
case v : IntOfIntOrString => Some( v.v )
case _ => None
}
}
object String {
def unapply( t : IntOrString ) : Option[String] = t match {
case v : StringOfIntOrString => Some( v.v )
case _ => None
}
}
def size( t : IntOrString ) = t match {
case Int(i) => i
case String(s) => s.length
}
scala> size("test")
res0: Int = 4
scala> size(2)
res1: Int = 2
Một vấn đề là Scala sẽ không sử dụng trong bối cảnh trường hợp phù hợp, một chuyển đổi ngầm từ IntOfIntOrString
đến Int
(và StringOfIntOrString
đến String
), vì vậy phải xác định nhổ và sử dụng case Int(i)
thay vì case i : Int
.
THÊM: Tôi đã trả lời Miles Sabin tại blog của anh ấy như sau. Có lẽ có một số cải tiến đối với Either:
- Nó mở rộng đến hơn 2 loại, không có bất kỳ tiếng ồn bổ sung nào tại trang web sử dụng hoặc định nghĩa.
- Các đối số được đóng hộp ngầm, ví dụ: không cần
size(Left(2))
hoặc size(Right("test"))
.
- Cú pháp của mô hình khớp hoàn toàn không được đóng hộp.
- Quyền anh và unboxing có thể được tối ưu hóa bằng điểm nóng JVM.
- Cú pháp có thể là một kiểu được thông qua bởi một loại kết hợp hạng nhất trong tương lai, vì vậy di chuyển có thể là liền mạch? Có lẽ đối với tên loại kết hợp, sẽ tốt hơn nếu sử dụng
V
thay vì Or
, ví dụ: IntVString
` Int |v| String
`, ` Int or String
` hoặc `` ưa thích của tôi Int|String
?
CẬP NHẬT: Phủ định logic của sự phân biệt cho mẫu ở trên và tôi đã thêm một mẫu thay thế (và có thể hữu ích hơn) tại blog của Miles Sabin .
sealed trait `Int or String`
sealed trait `not an Int or String`
sealed trait `Int|String`[T,E]
case class `IntOf(Int|String)`( v:Int ) extends `Int|String`[Int,`Int or String`]
case class `StringOf(Int|String)`( v:String ) extends `Int|String`[String,`Int or String`]
case class `NotAn(Int|String)`[T]( v:T ) extends `Int|String`[T,`not an Int or String`]
implicit def `IntTo(IntOf(Int|String))`( v:Int ) = new `IntOf(Int|String)`(v)
implicit def `StringTo(StringOf(Int|String))`( v:String ) = new `StringOf(Int|String)`(v)
implicit def `AnyTo(NotAn(Int|String))`[T]( v:T ) = new `NotAn(Int|String)`[T](v)
def disjunction[T,E](x: `Int|String`[T,E])(implicit ev: E =:= `Int or String`) = x
def negationOfDisjunction[T,E](x: `Int|String`[T,E])(implicit ev: E =:= `not an Int or String`) = x
scala> disjunction(5)
res0: Int|String[Int,Int or String] = IntOf(Int|String)(5)
scala> disjunction("")
res1: Int|String[String,Int or String] = StringOf(Int|String)()
scala> disjunction(5.0)
error: could not find implicit value for parameter ev: =:=[not an Int or String,Int or String]
disjunction(5.0)
^
scala> negationOfDisjunction(5)
error: could not find implicit value for parameter ev: =:=[Int or String,not an Int or String]
negationOfDisjunction(5)
^
scala> negationOfDisjunction("")
error: could not find implicit value for parameter ev: =:=[Int or String,not an Int or String]
negationOfDisjunction("")
^
scala> negationOfDisjunction(5.0)
res5: Int|String[Double,not an Int or String] = NotAn(Int|String)(5.0)
CẬP NHẬT KHÁC: Liên quan đến ý kiến 23 và 35 về giải pháp của Mile Sabin , đây là một cách để khai báo một loại kết hợp tại trang web sử dụng. Lưu ý rằng nó không được đóng hộp sau cấp độ đầu tiên, nghĩa là nó có lợi thế là có thể mở rộng cho bất kỳ số loại nào trong phân biệt , trong khi Either
nhu cầu quyền anh lồng nhau và mô hình trong nhận xét trước 41 của tôi không thể mở rộng. Nói cách khác, a D[Int ∨ String]
được gán cho (tức là một kiểu con của) a D[Int ∨ String ∨ Double]
.
type ¬[A] = (() => A) => A
type ∨[T, U] = ¬[T] with ¬[U]
class D[-A](v: A) {
def get[T](f: (() => T)) = v match {
case x : ¬[T] => x(f)
}
}
def size(t: D[Int ∨ String]) = t match {
case x: D[¬[Int]] => x.get( () => 0 )
case x: D[¬[String]] => x.get( () => "" )
case x: D[¬[Double]] => x.get( () => 0.0 )
}
implicit def neg[A](x: A) = new D[¬[A]]( (f: (() => A)) => x )
scala> size(5)
res0: Any = 5
scala> size("")
error: type mismatch;
found : java.lang.String("")
required: D[?[Int,String]]
size("")
^
scala> size("hi" : D[¬[String]])
res2: Any = hi
scala> size(5.0 : D[¬[Double]])
error: type mismatch;
found : D[(() => Double) => Double]
required: D[?[Int,String]]
size(5.0 : D[?[Double]])
^
Rõ ràng trình biên dịch Scala có ba lỗi.
- Nó sẽ không chọn chức năng ẩn chính xác cho bất kỳ loại nào sau loại đầu tiên trong phân biệt đích.
- Nó không loại trừ
D[¬[Double]]
trường hợp từ trận đấu.
3.
scala> class D[-A](v: A) {
def get[T](f: (() => T))(implicit e: A <:< ¬[T]) = v match {
case x : ¬[T] => x(f)
}
}
error: contravariant type A occurs in covariant position in
type <:<[A,(() => T) => T] of value e
def get[T](f: (() => T))(implicit e: A <:< ?[T]) = v match {
^
Phương thức get không bị ràng buộc đúng về kiểu đầu vào, bởi vì trình biên dịch sẽ không cho phép A
ở vị trí covariant. Người ta có thể lập luận rằng đó là một lỗi vì tất cả những gì chúng ta muốn là bằng chứng, chúng ta không bao giờ truy cập bằng chứng trong hàm. Và tôi đã lựa chọn không để thử nghiệm cho case _
trong get
phương pháp, vì vậy tôi sẽ không cần phải Unbox một Option
trong match
trong size()
.
Ngày 05 tháng 3 năm 2012: Bản cập nhật trước cần một sự cải tiến. Giải pháp của Miles Sabin hoạt động chính xác với phân nhóm.
type ¬[A] = A => Nothing
type ∨[T, U] = ¬[T] with ¬[U]
class Super
class Sub extends Super
scala> implicitly[(Super ∨ String) <:< ¬[Super]]
res0: <:<[?[Super,String],(Super) => Nothing] =
scala> implicitly[(Super ∨ String) <:< ¬[Sub]]
res2: <:<[?[Super,String],(Sub) => Nothing] =
scala> implicitly[(Super ∨ String) <:< ¬[Any]]
error: could not find implicit value for parameter
e: <:<[?[Super,String],(Any) => Nothing]
implicitly[(Super ? String) <:< ?[Any]]
^
Đề xuất cập nhật trước của tôi (đối với loại liên minh hạng nhất) đã phá vỡ phân nhóm.
scala> implicitly[D[¬[Sub]] <:< D[(Super ∨ String)]]
error: could not find implicit value for parameter
e: <:<[D[(() => Sub) => Sub],D[?[Super,String]]]
implicitly[D[?[Sub]] <:< D[(Super ? String)]]
^
Vấn đề là A
trong (() => A) => A
xuất hiện trong cả hai hiệp biến (kiểu trả về) và contravariant (chức năng đầu vào, hoặc trong trường hợp này một giá trị trả về của hàm là một chức năng đầu vào) vị trí, do đó thay thế có thể chỉ là bất biến.
Lưu ý rằng điều đó A => Nothing
chỉ cần thiết bởi vì chúng tôi muốn A
ở vị trí chống chỉ định, do đó các siêu kiểu A
không phải là kiểu con D[¬[A]]
cũng không D[¬[A] with ¬[U]]
( xem thêm ). Vì chúng tôi chỉ cần chống chỉ định gấp đôi, chúng tôi có thể đạt được giải pháp tương đương với Miles ngay cả khi chúng tôi có thể loại bỏ ¬
và ∨
.
trait D[-A]
scala> implicitly[D[D[Super]] <:< D[D[Super] with D[String]]]
res0: <:<[D[D[Super]],D[D[Super] with D[String]]] =
scala> implicitly[D[D[Sub]] <:< D[D[Super] with D[String]]]
res1: <:<[D[D[Sub]],D[D[Super] with D[String]]] =
scala> implicitly[D[D[Any]] <:< D[D[Super] with D[String]]]
error: could not find implicit value for parameter
e: <:<[D[D[Any]],D[D[Super] with D[String]]]
implicitly[D[D[Any]] <:< D[D[Super] with D[String]]]
^
Vì vậy, sửa chữa hoàn chỉnh là.
class D[-A] (v: A) {
def get[T <: A] = v match {
case x: T => x
}
}
implicit def neg[A](x: A) = new D[D[A]]( new D[A](x) )
def size(t: D[D[Int] with D[String]]) = t match {
case x: D[D[Int]] => x.get[D[Int]].get[Int]
case x: D[D[String]] => x.get[D[String]].get[String]
case x: D[D[Double]] => x.get[D[Double]].get[Double]
}
Lưu ý 2 lỗi trước trong Scala vẫn còn, nhưng lỗi thứ 3 được tránh vì T
hiện tại bị hạn chế là kiểu con của A
.
Chúng tôi có thể xác nhận các công việc phụ.
def size(t: D[D[Super] with D[String]]) = t match {
case x: D[D[Super]] => x.get[D[Super]].get[Super]
case x: D[D[String]] => x.get[D[String]].get[String]
}
scala> size( new Super )
res7: Any = Super@1272e52
scala> size( new Sub )
res8: Any = Sub@1d941d7
Tôi đã suy nghĩ rằng các loại giao lộ đầu tiên-lớp là rất quan trọng, cả cho lý do Ceylon có họ , và bởi vì thay vì gộp vào Any
đó phương tiện unboxing với một match
đối với các loại dự kiến có thể tạo ra một lỗi thời gian chạy, unboxing của một ( bộ sưu tập đồng nhất chứa a) phân tách có thể được kiểm tra loại (Scala phải sửa các lỗi tôi đã lưu ý). Các hiệp hội đơn giản hơn so với sự phức tạp của việc sử dụng HList thử nghiệm của metascala cho các bộ sưu tập không đồng nhất.
class StringOrInt[T]
được thực hiệnsealed
, "rò rỉ" mà bạn đã đề cập ("Tất nhiên, điều này có thể được mã bên cạnh bởi mã máy khách bằng cách tạoStringOrInt[Boolean]
") được cắm, ít nhất là nếuStringOrInt
nằm trong một tệp của chính nó. Sau đó, các đối tượng chứng kiến phải được xác định trong cùng một souce nhưStringOrInt
.