Làm thế nào để xác định loại phân biệt chủng loại khác (loại liên minh)?


181

Một cách đã được đề xuất để đối phó với các định nghĩa kép về các phương thức bị quá tải là thay thế quá tải bằng khớp mẫu:

object Bar {
   def foo(xs: Any*) = xs foreach { 
      case _:String => println("str")
      case _:Int => println("int")
      case _ => throw new UglyRuntimeException()
   }
}

Cách tiếp cận này yêu cầu chúng ta đầu hàng kiểu tĩnh kiểm tra các đối số foo. Nó sẽ đẹp hơn nhiều để có thể viết

object Bar {
   def foo(xs: (String or Int)*) = xs foreach {
      case _: String => println("str")
      case _: Int => println("int")
   }
}

Tôi có thể gần gũi với Eithernó, nhưng nó trở nên xấu xí nhanh chóng với hơn hai loại:

type or[L,R] = Either[L,R]

implicit def l2Or[L,R](l: L): L or R = Left(l)
implicit def r2Or[L,R](r: R): L or R = Right(r)

object Bar {
   def foo(xs: (String or Int)*) = xs foreach {
      case Left(l) => println("str")
      case Right(r) => println("int")
   }
}

Nó trông giống như một vị tướng (tao nhã, hiệu quả) giải pháp sẽ yêu cầu xác định Either3, Either4, .... Có ai biết của một giải pháp thay thế để đạt được cùng một mục? Theo hiểu biết của tôi, Scala không có "phân biệt kiểu" tích hợp. Ngoài ra, các chuyển đổi ngầm định được xác định ở trên có ẩn trong thư viện chuẩn ở đâu đó để tôi có thể nhập chúng không?

Câu trả lời:


142

Chà, trong trường hợp cụ thể Any*, thủ thuật dưới đây sẽ không hiệu quả, vì nó sẽ không chấp nhận các loại hỗn hợp. Tuy nhiên, vì các loại hỗn hợp sẽ không hoạt động với quá tải, đây có thể là những gì bạn muốn.

Đầu tiên, khai báo một lớp với các loại bạn muốn chấp nhận như dưới đây:

class StringOrInt[T]
object StringOrInt {
  implicit object IntWitness extends StringOrInt[Int]
  implicit object StringWitness extends StringOrInt[String]
}

Tiếp theo, tuyên bố foonhư thế này:

object Bar {
  def foo[T: StringOrInt](x: T) = x match {
    case _: String => println("str")
    case _: Int => println("int")
  }
}

Và đó là nó. Bạn có thể gọi foo(5)hoặc foo("abc"), và nó sẽ hoạt động, nhưng hãy thử foo(true)và nó sẽ thất bại. Điều này có thể được đặt cạnh bởi mã máy khách bằng cách tạo một StringOrInt[Boolean], trừ khi, như Randall lưu ý bên dưới, bạn tạo StringOrIntmột sealedlớp.

Nó hoạt động bởi vì T: StringOrIntcó nghĩa là có một tham số ngầm định của loại StringOrInt[T]và vì Scala nhìn vào bên trong các đối tượng đồng hành của một loại để xem liệu có ẩn ý ở đó để làm cho mã yêu cầu loại đó hoạt động không.


14
Nếu class StringOrInt[T]được thực hiện sealed, "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ạo StringOrInt[Boolean]") được cắm, ít nhất là nếu StringOrIntnằ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.
Randall Schulz

3
Tôi đã cố gắng khái quát hóa giải pháp này phần nào (được đăng dưới dạng câu trả lời bên dưới). Hạn chế chính so với Eithercách tiếp cận dường như là chúng ta mất rất nhiều hỗ trợ trình biên dịch để kiểm tra trận đấu.
Aaron Novstrup

Bí quyết đẹp! Tuy nhiên, ngay cả với lớp được niêm phong, bạn vẫn có thể phá vỡ nó trong mã máy khách bằng cách xác định một giá trị ẩn b = new StringOrInt [Boolean] trong phạm vi với foo hoặc bằng cách gọi rõ ràng foo (2.9) (StringOrInt [Double] mới). Tôi nghĩ bạn cần phải làm cho lớp trừu tượng là tốt.
Paolo Falabella

2
Đúng; nó có lẽ sẽ tốt hơn để sử dụngtrait StringOrInt ...
Ốc cơ khí

7
Nếu bạn muốn hỗ trợ các kiểu con, chỉ cần thay đổi StringOrInt[T]thành StringOrInt[-T](xem stackoverflow.com/questions/24387701/ mẹo )
Eran Medan

178

Miles Sabin mô tả một cách rất hay để có được loại kết hợp trong bài đăng trên blog gần đây của mình Các loại kết hợp không được đóng hộp trong Scala thông qua sự đồng hình của Curry-Howard :

Đầu tiên ông định nghĩa phủ định các loại là

type ¬[A] = A => Nothing

sử dụng luật của De Morgan, điều này cho phép anh ta xác định các loại liên minh

type[T, U] = ¬[¬[T] with ¬[U]]

Với các cấu trúc phụ trợ sau

type ¬¬[A] = ¬[¬[A]]
type ||[T, U] = { type λ[X] = ¬¬[X] <:< (T ∨ U) }

bạn có thể viết các loại kết hợp như sau:

def size[T : (Int || String)#λ](t : T) = t match {
    case i : Int => i
    case s : String => s.length
}

13
Đó là một trong những điều tuyệt vời nhất tôi từng thấy.
Submonoid

18
Dưới đây là triển khai mở rộng ý tưởng của Miles: github.com/GenslerAppsPod/scalavro/blob/master/util/src/main/ . - với các ví dụ: github.com/GenslerAppsPod/scalavro/blob/master/util/src/ kiểm tra / đào
Connor Doyle

6
Nhận xét trên nên là một câu trả lời trên chính nó. Đây chỉ là một triển khai ý tưởng của Miles, nhưng được gói gọn trong một gói trên Maven Central, và không có tất cả các biểu tượng unicode có thể (?) Gây ra vấn đề cho một thứ gì đó trong quá trình xây dựng ở đâu đó.
Jim Pivarski

2
Nhân vật hài hước đó là phủ định boolean .
michid

1
Ban đầu, ý tưởng có vẻ quá phức tạp với tôi. Đọc hầu hết mọi liên kết được đề cập trong chủ đề này, tôi đã bị cuốn hút bởi ý tưởng và vẻ đẹp của việc thực hiện nó :-) ... nhưng tôi vẫn cảm thấy như đây là một thứ gì đó bị xáo trộn ... bây giờ chỉ vì nó chưa có sẵn tránh xa Scala Như Miles nói: "Bây giờ chúng tôi chỉ cần làm phiền Martin và Adriaan để làm cho nó có thể truy cập trực tiếp."
Richard Gomes

44

Dotty , một trình biên dịch Scala thử nghiệm mới, hỗ trợ các loại kết hợp (bằng văn bản A | B), vì vậy bạn có thể làm chính xác những gì bạn muốn:

def foo(xs: (String | Int)*) = xs foreach {
   case _: String => println("str")
   case _: Int => println("int")
}

1
Một trong những ngày này.
Michael Ahlers

5
Nhân tiện, Dotty sẽ là scala 3 mới (nó đã được công bố vài tháng trước).
6infinity8

1
và sẽ có mặt ở đâu đó vào cuối năm 2020
JulienD

31

Đây là cách Rex Kerr để mã hóa các loại kết hợp. Thẳng và đơn giản!

scala> def f[A](a: A)(implicit ev: (Int with String) <:< A) = a match {
     |   case i: Int => i + 1
     |   case s: String => s.length
     | }
f: [A](a: A)(implicit ev: <:<[Int with String,A])Int

scala> f(3)
res0: Int = 4

scala> f("hello")
res1: Int = 5

scala> f(9.2)
<console>:9: error: Cannot prove that Int with String <:< Double.
       f(9.2)
        ^

Nguồn: Nhận xét # 27 dưới bài đăng blog tuyệt vời này của Miles Sabin cung cấp một cách khác để mã hóa các loại kết hợp trong Scala.


6
Thật không may, mã hóa này có thể bị đánh bại: scala> f(9.2: AnyVal)vượt qua máy đánh chữ.
Kipton Barros

@Kipton: Thật đáng buồn. Liệu mã hóa của Miles Sabin cũng gặp phải vấn đề này?
năm11

9
Có một phiên bản mã Miles đơn giản hơn một chút; vì anh ta thực sự sử dụng hàm ý ngược của tham số contravariant của hàm, không phải là "không" nghiêm ngặt, bạn có thể sử dụng trait Contra[-A] {}thay cho tất cả các hàm thành không có gì. Vì vậy, bạn nhận được những thứ như type Union[A,B] = { type Check[Z] = Contra[Contra[Z]] <:< Contra[Contra[A] with Contra[B]] }được sử dụng như def f[T: Union[Int, String]#Check](t: T) = t match { case i: Int => i; case s: String => s.length }(không có unicode ưa thích).
Rex Kerr

Điều này có thể giải quyết vấn đề thừa kế của các loại công đoàn? stackoverflow.com/questions/45255270/
Mạnh

Hmm, tôi đã thử nó, tôi không thể tạo các kiểu trả về với bảng mã này, do đó dường như không thể thực hiện phân nhóm stackoverflow.com/questions/45255270/
jhegedus 22/07/17

18

Có thể khái quát hóa giải pháp của Daniel như sau:

sealed trait Or[A, B]

object Or {
   implicit def a2Or[A,B](a: A) = new Or[A, B] {}
   implicit def b2Or[A,B](b: B) = new Or[A, B] {}
}

object Bar {
   def foo[T <% String Or Int](x: T) = x match {
     case _: String => println("str")
     case _: Int => println("int")
   }
}

Nhược điểm chính của phương pháp này là

  • Như Daniel đã chỉ ra, nó không xử lý các bộ sưu tập / varargs với các loại hỗn hợp
  • Trình biên dịch không đưa ra cảnh báo nếu trận đấu không đầy đủ
  • Trình biên dịch không phát sinh lỗi nếu khớp bao gồm trường hợp không thể
  • Giống như Eithercách tiếp cận, khái quát hơn nữa đòi hỏi phải xác định tương tự Or3, Or4vv đặc điểm. Tất nhiên, việc xác định các đặc điểm như vậy sẽ đơn giản hơn nhiều so với việc xác định các Eitherlớp tương ứng .

Cập nhật:

Mitch Blevins thể hiện một cách tiếp cận rất giống nhau và chỉ ra cách khái quát hóa nó thành nhiều hơn hai loại, gọi nó là "nói lắp hoặc".


17

Tôi đã vấp phải sự triển khai tương đối sạch sẽ của các loại liên minh n-ary bằng cách kết hợp khái niệm danh sách loại với đơn giản hóa công việc của Miles Sabin trong lĩnh vực này , mà ai đó đề cập đến trong câu trả lời khác.

Loại nhất định ¬[-A]mà được contravariant trên A, theo định nghĩa cho A <: Bchúng ta có thể viết ¬[B] <: ¬[A], đảo ngược thứ tự của các loại.

Cho các loại A, BX, chúng tôi muốn thể hiện X <: A || X <: B. Áp dụng chống chỉ định, chúng tôi nhận được ¬[A] <: ¬[X] || ¬[B] <: ¬[X]. Lon trong sự thay đổi này được thể hiện như ¬[A] with ¬[B] <: ¬[X]trong đó một trong Ahoặc Bphải là một siêu kiểu của Xhoặc Xbản thân (suy nghĩ về đối số chức năng).

object Union {
  import scala.language.higherKinds

  sealed trait ¬[-A]

  sealed trait TSet {
    type Compound[A]
    type Map[F[_]] <: TSet
  }

  sealed traitextends TSet {
    type Compound[A] = A
    type Map[F[_]] =}

  // Note that this type is left-associative for the sake of concision.
  sealed trait[T <: TSet, H] extends TSet {
    // Given a type of the form `∅ ∨ A ∨ B ∨ ...` and parameter `X`, we want to produce the type
    // `¬[A] with ¬[B] with ... <:< ¬[X]`.
    type Member[X] = T#Map[¬]#Compound[¬[H]] <:< ¬[X]

    // This could be generalized as a fold, but for concision we leave it as is.
    type Compound[A] = T#Compound[H with A]

    type Map[F[_]] = T#Map[F] ∨ F[H]
  }

  def foo[A : (∅ ∨ StringIntList[Int])#Member](a: A): String = a match {
    case s: String => "String"
    case i: Int => "Int"
    case l: List[_] => "List[Int]"
  }

  foo(42)
  foo("bar")
  foo(List(1, 2, 3))
  foo(42d) // error
  foo[Any](???) // error
}

Tôi đã dành thời gian cố gắng để kết hợp ý tưởng này với một trên ràng buộc trên các loại thành viên như đã thấy trong TLists của harrah / lên , tuy nhiên việc thực hiện Mapvới loại giới hạn đã vậy, đến nay đã chứng minh thách thức.


1
Điều này thật tuyệt vời, cảm ơn bạn! Tôi đã thử các cách tiếp cận trước đó nhưng vẫn gặp vấn đề khi sử dụng phương pháp này với các loại chung như là một phần của liên minh. Đây là triển khai duy nhất mà tôi có thể làm việc với các loại chung chung.
Samer Adra

Đáng buồn thay, nhưng có lẽ được mong đợi, khi tôi cố gắng sử dụng một phương thức Scala có kiểu kết hợp từ mã Java, nó không hoạt động. Lỗi: (40, 29) java: phương thức setValue trong lớp Cấu hình không thể được áp dụng cho các kiểu đã cho; bắt buộc: X, scala.Predef. $ less $ $ $ <UnionTypes.package. biến loại (s) X (danh sách đối số thực tế và chính thức khác nhau về độ dài)
Samer Adra

Vẫn chưa rõ ràng về một số chi tiết trong việc thực hiện này. Ví dụ: bài viết gốc đã định nghĩa phủ định là "loại ¬ [A] = A => Không có gì" nhưng trong phiên bản này nếu chỉ có "đặc điểm kín ¬ [-A]" và đặc điểm không được mở rộng ở bất cứ đâu. Cái này hoạt động ra sao?
Samer Adra

@Samer Adra Nó sẽ hoạt động theo cách nào đó, bài viết sử dụng Function1như một loại chống chỉ định hiện có. Bạn không cần triển khai, tất cả những gì bạn cần là bằng chứng về sự phù hợp ( <:<).
J Cracknell

Bất kỳ ý tưởng làm thế nào để có một nhà xây dựng chấp nhận một loại công đoàn?
Samer Adra

12

Một giải pháp lớp loại có lẽ là cách tốt nhất để đi đến đây, sử dụng ẩn ý. Điều này tương tự như cách tiếp cận đơn hình được đề cập trong cuốn sách Oderky / Spoon / Venners:

abstract class NameOf[T] {
  def get : String
}

implicit object NameOfStr extends NameOf[String] {
  def get = "str"
}

implicit object NameOfInt extends NameOf[Int] {
 def get = "int"
}

def printNameOf[T](t:T)(implicit name : NameOf[T]) = println(name.get)

Nếu sau đó bạn chạy nó trong REPL:

scala> printNameOf(1)
int

scala> printNameOf("sss")
str

scala> printNameOf(2.0f)
<console>:10: error: could not find implicit value for parameter nameOf: NameOf[
Float]
       printNameOf(2.0f)

              ^

Tôi có thể sai, nhưng tôi không nghĩ đây là điều mà OP đang tìm kiếm. OP đã hỏi về một loại dữ liệu có thể đại diện cho sự kết hợp các loại khác nhau và sau đó thực hiện phân tích trường hợp về nó trong thời gian chạy để xem loại thực tế hóa ra là gì. Các lớp loại sẽ không giải quyết được vấn đề này, vì chúng là một cấu trúc thời gian biên dịch hoàn toàn.
Tom Crockett

4
Câu hỏi thực sự được đặt ra là làm thế nào để phơi bày hành vi khác nhau cho các loại khác nhau, nhưng không quá tải. Không có kiến ​​thức về các lớp loại (và có lẽ một số tiếp xúc với C / C ++), một loại kết hợp dường như là giải pháp duy nhất. EitherKiểu có sẵn của Scala có xu hướng củng cố niềm tin này. Sử dụng các lớp loại thông qua hàm ý của Scala là một giải pháp tốt hơn cho vấn đề tiềm ẩn, nhưng đó là một khái niệm tương đối mới và vẫn chưa được biết đến rộng rãi, đó là lý do tại sao OP thậm chí không biết coi chúng là một giải pháp thay thế khả dĩ cho loại liên minh.
Kevin Wright

cái này có hoạt động với phân nhóm không? stackoverflow.com/questions/45255270/
hy

10

Chúng tôi muốn một toán tử loại Or[U,V]có thể được sử dụng để ràng buộc một tham số loại Xtheo cách X <: Uhoặc X <: V. Đây là một định nghĩa gần giống như chúng ta có thể nhận được:

trait Inv[-X]
type Or[U,T] = {
    type pf[X] = (Inv[U] with Inv[T]) <:< Inv[X]
}

Đây là cách nó được sử dụng:

// use

class A; class B extends A; class C extends B

def foo[X : (B Or String)#pf] = {}

foo[B]      // OK
foo[C]      // OK
foo[String] // OK
foo[A]      // ERROR!
foo[Number] // ERROR!

Điều này sử dụng một vài thủ thuật kiểu Scala. Cái chính là sử dụng các ràng buộc kiểu tổng quát . Các kiểu đã cho UV, trình biên dịch Scala cung cấp một lớp được gọi U <:< V(và một đối tượng ẩn của lớp đó) khi và chỉ khi trình biên dịch Scala có thể chứng minh đó Ulà một kiểu con của V. Đây là một ví dụ đơn giản hơn bằng cách sử dụng các ràng buộc kiểu tổng quát hoạt động trong một số trường hợp:

def foo[X](implicit ev : (B with String) <:< X) = {}

Ví dụ này hoạt động khi Xmột thể hiện của lớp B, a Stringhoặc có một loại không phải là siêu kiểu cũng không phải là kiểu con của Bhoặc String. Trong hai trường hợp đầu tiên, điều đó đúng với định nghĩa của withtừ khóa đó (B with String) <: B(B with String) <: Stringvì vậy Scala sẽ cung cấp một đối tượng ẩn sẽ được truyền vào dưới dạng ev: trình biên dịch Scala sẽ chấp nhận chính xác foo[B]foo[String].

Trong trường hợp cuối cùng, tôi dựa vào thực tế là nếu U with V <: X, thì U <: Xhay V <: X. Có vẻ như trực giác là đúng, và tôi chỉ đơn giản là giả định nó. Rõ ràng từ giả định này tại sao ví dụ đơn giản này thất bại khi Xsiêu kiểu hoặc kiểu con của một trong hai Bhoặc String: ví dụ, trong ví dụ trên, foo[A]được chấp nhận không chính xác và foo[C]bị từ chối không chính xác. Một lần nữa, những gì chúng tôi muốn là một số loại loại biểu hiện trên các biến U, VXđó là sự thật chính xác khi nào X <: Uhay X <: V.

Khái niệm chống đối của Scala có thể giúp đỡ ở đây. Ghi nhớ đặc điểm trait Inv[-X]? Bởi vì nó là chống chỉ định trong tham số loại của nó X, Inv[X] <: Inv[Y]nếu và chỉ khi Y <: X. Điều đó có nghĩa là chúng ta có thể thay thế ví dụ trên bằng một ví dụ thực sự sẽ hoạt động:

trait Inv[-X]
def foo[X](implicit ev : (Inv[B] with Inv[String]) <:< Inv[X]) = {}

Đó là bởi vì biểu thức (Inv[U] with Inv[V]) <: Inv[X]là đúng, bởi cùng một giả định ở trên, chính xác là khi Inv[U] <: Inv[X]hoặc Inv[V] <: Inv[X], và theo định nghĩa của chống chỉ định, điều này đúng chính xác khi X <: Uhoặc X <: V.

Có thể làm cho mọi thứ có thể tái sử dụng nhiều hơn một chút bằng cách khai báo một loại tham số BOrString[X]và sử dụng nó như sau:

trait Inv[-X]
type BOrString[X] = (Inv[B] with Inv[String]) <:< Inv[X]
def foo[X](implicit ev : BOrString[X]) = {}

Scala bây giờ sẽ cố gắng xây dựng kiểu BOrString[X]cho mọi Xthứ foođược gọi với, và kiểu sẽ được xây dựng chính xác khi Xlà một kiểu con của một trong hai Bhoặc String. Điều đó làm việc, và có một ký hiệu tốc ký. Cú pháp dưới đây là tương đương (ngoại trừ evbây giờ phải được tham chiếu trong thân phương thức implicitly[BOrString[X]]chứ không phải đơn giản ev) và sử dụng BOrStringlàm ngữ cảnh kiểu ràng buộc :

def foo[X : BOrString] = {}

Những gì chúng tôi thực sự muốn là một cách linh hoạt để tạo ra một bối cảnh kiểu bị ràng buộc. Một bối cảnh loại phải là một loại có thể tham số và chúng tôi muốn một cách có thể tham số để tạo một bối cảnh. Nghe có vẻ như chúng ta đang cố gắng để cà ri các chức năng trên các loại giống như chúng ta thực hiện các chức năng trên các giá trị. Nói cách khác, chúng tôi muốn một cái gì đó như sau:

type Or[U,T][X] = (Inv[U] with Inv[T]) <:< Inv[X]

Điều đó không thể trực tiếp trong Scala, nhưng có một mẹo chúng ta có thể sử dụng để đến gần. Điều đó đưa chúng ta đến định nghĩa Orở trên:

trait Inv[-X]
type Or[U,T] = {
    type pf[X] = (Inv[U] with Inv[T]) <:< Inv[X]
}

Ở đây chúng tôi sử dụng kiểu gõ cấu trúctoán tử pound của Scala để tạo ra một kiểu cấu trúc Or[U,T]được đảm bảo có một kiểu bên trong. Đây là một con thú kỳ lạ. Để đưa ra một số bối cảnh, hàm def bar[X <: { type Y = Int }](x : X) = {}phải được gọi với các lớp con AnyRefcó loại Yđược định nghĩa trong chúng:

bar(new AnyRef{ type Y = Int }) // works!

Sử dụng toán tử pound cho phép chúng ta tham chiếu loại bên trong Or[B, String]#pfvà sử dụng ký hiệu infix cho toán tử loại Or, chúng ta đi đến định nghĩa ban đầu của chúng ta về foo:

def foo[X : (B Or String)#pf] = {}

Chúng ta có thể sử dụng thực tế là các loại hàm là chống chỉ định trong tham số loại đầu tiên của chúng để tránh xác định đặc điểm Inv:

type Or[U,T] = {
    type pf[X] = ((U => _) with (T => _)) <:< (X => _)
} 

Điều này có thể giải quyết A|B <: A|B|Cvấn đề? stackoverflow.com/questions/45255270/ Đổi tôi không thể biết.
jhegedus


7

Bạn có thể xem MetaScala , có một cái gì đó được gọi là OneOf. Tôi có ấn tượng rằng điều này không hoạt động tốt với các matchcâu lệnh nhưng bạn có thể mô phỏng khớp bằng các hàm bậc cao hơn. Ví dụ, hãy xem đoạn trích này , nhưng lưu ý rằng phần "kết hợp mô phỏng" được nhận xét, có thể vì nó chưa hoàn toàn hoạt động.

Bây giờ đối với một số biên tập: Tôi không nghĩ có bất cứ điều gì nghiêm trọng về việc xác định Either3, Either4, v.v. như bạn mô tả. Điều này về cơ bản là kép đối với 22 loại tuple tiêu chuẩn được tích hợp trong Scala. Chắc chắn sẽ rất tuyệt nếu Scala có các kiểu phân biệt được tích hợp sẵn, và có lẽ một số cú pháp hay cho chúng như thế {x, y, z}.


6

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:

  1. 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.
  2. Các đối số được đóng hộp ngầm, ví dụ: không cần size(Left(2))hoặc size(Right("test")).
  3. Cú pháp của mô hình khớp hoàn toàn không được đóng hộp.
  4. Quyền anh và unboxing có thể được tối ưu hóa bằng điểm nóng JVM.
  5. 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 Vthay 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 Eithernhu 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[IntString]) = 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.

  1. 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.
  2. 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 getphương pháp, vì vậy tôi sẽ không cần phải Unbox một Optiontrong matchtrong 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[(SuperString) <:< ¬[Super]]
res0: <:<[?[Super,String],(Super) => Nothing] = 

scala> implicitly[(SuperString) <:< ¬[Sub]]
res2: <:<[?[Super,String],(Sub) => Nothing] = 

scala> implicitly[(SuperString) <:< ¬[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[(SuperString)]]
error: could not find implicit value for parameter
       e: <:<[D[(() => Sub) => Sub],D[?[Super,String]]]
       implicitly[D[?[Sub]] <:< D[(Super ? String)]]
                 ^

Vấn đề là Atrong (() => A) => Axuấ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 => Nothingchỉ 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ỏ ¬.

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ì Thiệ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.


Mục số 3 ở trên không phải là một lỗi trong trình biên dịch Scala . Lưu ý ban đầu tôi đã không đánh số đó là một lỗi, sau đó bất cẩn thực hiện chỉnh sửa ngày hôm nay và đã làm như vậy (quên đi lý do ban đầu của tôi vì không nêu rõ đó là một lỗi). Tôi đã không chỉnh sửa bài đăng một lần nữa, vì tôi đang ở giới hạn 7 lần chỉnh sửa.
Shelby Moore III

Lỗi số 1 ở trên có thể tránh được với một công thức khác của sizehàm .
Shelby Moore III

Mục số 2 không phải là một lỗi. Scala không thể thể hiện đầy đủ một loại kết hợp . Tài liệu được liên kết cung cấp một phiên bản mã khác, để sizekhông còn chấp nhận D[Any]làm đầu vào.
Shelby Moore III

Tôi hoàn toàn không nhận được câu trả lời này, đây cũng là một câu trả lời cho câu hỏi này: stackoverflow.com/questions/45255270/
mẹo

5

Có một cách khác dễ hiểu hơn một chút nếu bạn không mò mẫm Curry-Howard:

type v[A,B] = Either[Option[A], Option[B]]

private def L[A,B](a: A): v[A,B] = Left(Some(a))
private def R[A,B](b: B): v[A,B] = Right(Some(b))  
// TODO: for more use scala macro to generate this for up to 22 types?
implicit def a2[A,B](a: A): v[A,B] = L(a)
implicit def b2[A,B](b: B): v[A,B] = R(b)
implicit def a3[A,B,C](a: A): v[v[A,B],C] = L(a2(a))
implicit def b3[A,B,C](b: B): v[v[A,B],C] = L(b2(b))
implicit def a4[A,B,C,D](a: A): v[v[v[A,B],C],D] = L(a3(a))
implicit def b4[A,B,C,D](b: B): v[v[v[A,B],C],D] = L(b3(b))    
implicit def a5[A,B,C,D,E](a: A): v[v[v[v[A,B],C],D],E] = L(a4(a))
implicit def b5[A,B,C,D,E](b: B): v[v[v[v[A,B],C],D],E] = L(b4(b))

type JsonPrimtives = (String v Int v Double)
type ValidJsonPrimitive[A] = A => JsonPrimtives

def test[A : ValidJsonPrimitive](x: A): A = x 

test("hi")
test(9)
// test(true)   // does not compile

Tôi sử dụng kỹ thuật tương tự trong dijon


Điều này có thể làm việc với subtyping? Cảm giác ruột của tôi: không, nhưng tôi có thể sai. stackoverflow.com/questions/45255270/
Mạnh

1

Chà, tất cả đều rất thông minh, nhưng tôi khá chắc chắn rằng bạn đã biết rằng câu trả lời cho câu hỏi hàng đầu của bạn là nhiều loại "Không". Scala xử lý quá tải khác nhau và, nó phải được thừa nhận, có phần kém thanh lịch hơn bạn mô tả. Một số trong số đó là do khả năng tương tác của Java, một số nguyên nhân là do không muốn gặp các trường hợp bị cắt của thuật toán suy luận kiểu và một số do đơn giản là nó không phải là Haskell.


5
Trong khi tôi đã sử dụng Scala được một thời gian, tôi không hiểu biết cũng không thông minh như bạn nghĩ. Trong ví dụ này, tôi có thể thấy một thư viện có thể cung cấp giải pháp như thế nào. Nó có ý nghĩa để sau đó tự hỏi liệu một thư viện như vậy tồn tại (hoặc một số thay thế).
Aaron Novstrup

1

Thêm vào các câu trả lời đã tuyệt vời ở đây. Đây là một ý chính xây dựng trên các loại kết hợp Miles Sabin (và ý tưởng của Josh) nhưng cũng khiến chúng được định nghĩa đệ quy, do đó bạn có thể có> 2 loại trong liên minh ( def foo[A : UNil Or Int Or String Or List[String])

https://gist.github.com/aishfenton/2bb3bfa12e0321acfc904a71dda9bfbb

Lưu ý: Tôi nên thêm rằng sau khi chơi xung quanh với một dự án ở trên, cuối cùng tôi đã quay trở lại các kiểu tổng cũ (nghĩa là đặc điểm kín với các lớp con). Các loại kết hợp Miles Sabin là tuyệt vời để hạn chế tham số loại, nhưng nếu bạn cần trả về loại kết hợp thì nó không cung cấp nhiều.


Điều này có thể giải quyết A|C <: A|B|Cvấn đề phân nhóm? stackoverflow.com/questions/45255270/ Từ ruột của tôi cảm thấy KHÔNG bởi vì điều đó có nghĩa là nó A or Csẽ cần phải là kiểu con của (A or B) or Cnhưng nó không chứa loại A or Cnên không có hy vọng tạo ra A or Cmột kiểu con A or B or Cvới mã hóa này ít nhất .. . bạn nghĩ sao ?
jhegedus

0

Từ các tài liệu , với việc bổ sung sealed:

sealed class Expr
case class Var   (x: String)          extends Expr
case class Apply (f: Expr, e: Expr)   extends Expr
case class Lambda(x: String, e: Expr) extends Expr

Về sealedphần:

Có thể định nghĩa các lớp trường hợp tiếp theo mở rộng loại Expr trong các phần khác của chương trình (...). Hình thức mở rộng này có thể được loại trừ bằng cách khai báo lớp cơ sở Expr niêm phong; trong trường hợp này, tất cả các lớp mở rộng trực tiếp Expr phải nằm trong cùng một tệp nguồn như Expr.

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.