首页 文章

如何定义“类型析取”(联合类型)?

提问于
浏览
166

使用been suggested来处理重载方法的双重定义的一种方法是用模式匹配替换重载:

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

这种方法要求我们放弃对 foo 的参数的静态类型检查 . 能够写作会好得多

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

我可以接近 Either ,但它有两种以上类型的快速丑陋:

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")
   }
}

看起来像一般(优雅,高效)的解决方案需要定义 Either3Either4 ,....有没有人知道实现相同目的的替代解决方案?据我所知,Scala没有内置的"type disjunction" . 另外,上面定义的隐式转换是否隐藏在某个标准库中,以便我可以导入它们?

15 回答

  • 130

    好吧,在 Any* 的特定情况下,下面的这个技巧也会超载,这可能就是你想要的 .

    首先,声明一个包含您希望接受的类的类,如下所示:

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

    接下来,声明 foo 如下:

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

    就是这样 . 您可以调用 foo(5)foo("abc") ,它会起作用,但请尝试 foo(true) 并且它将失败 . 这可以通过创建 StringOrInt[Boolean] 由客户端代码支持,除非,如下面的Randall所示,您使 StringOrIntsealed 类 .

    它的工作原理是因为 T: StringOrInt 意味着有一个类型为 StringOrInt[T] 的隐式参数,并且因为Scala在一个类型的伴随对象中查看是否存在implicits,以使代码要求该类型起作用 .

  • 14

    Miles Sabin在他最近的博客文章_1697152中描述了一种非常好的方式来获得联盟类型:

    他首先将类型否定定义为

    type ¬[A] = A => Nothing
    

    使用De Morgan定律,这允许他定义联合类型

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

    使用以下辅助构造

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

    您可以按如下方式编写联合类型:

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

    Dotty,一个新的实验性Scala编译器,支持联合类型(写成 A | B ),因此您可以完全按照自己的意愿执行操作:

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

    以下是Rex Kerr编码联合类型的方法 . 直接而简单!

    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)
            ^
    

    Source: 评论#27在Miles Sabin的this优秀博客文章中提供了另一种在Scala中编码联合类型的方法 .

  • 4

    可以如下概括Daniel's solution

    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")
       }
    }
    

    这种方法的主要缺点是

    • 正如丹尼尔指出的那样,它不处理混合类型的集合/变量

    • 如果匹配不详尽,编译器不会发出警告

    • 如果匹配包含不可能的情况,编译器不会发出错误

    • Either 方法类似,进一步的泛化需要定义类似的 Or3Or4 等特征 . 当然,定义这些特征比定义相应的 Either 类要简单得多 .

    Update:

    Mitch Blevins demonstrates a very similar approach并展示如何将其概括为两种以上类型,将其复制为"stuttering or" .

  • 1

    通过将类型列表的概念与Miles Sabin's work in this area的简化相结合,我有点偶然发现n-ary联合类型的相对干净的实现,有人在另一个答案中提到 .

    给定类型 ¬[-A] ,它在 A 上是逆变的,根据给定 A <: B 的定义,我们可以写 ¬[B] <: ¬[A] ,反转类型的排序 .

    给定 ABX 类型,我们想表达 X <: A || X <: B . 应用逆变,我们得到 ¬[A] <: ¬[X] || ¬[B] <: ¬[X] . 这可以表示为 ¬[A] with ¬[B] <: ¬[X] ,其中 AB 之一必须是 XX 本身的超类型(考虑函数参数) .

    object Union {
      import scala.language.higherKinds
    
      sealed trait ¬[-A]
    
      sealed trait TSet {
        type Compound[A]
        type Map[F[_]] <: TSet
      }
    
      sealed trait ∅ extends 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 : (∅ ∨ String ∨ Int ∨ List[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
    }
    

    我确实花了一些时间尝试将这个想法与成员类型的上限结合起来,如harrah/up的_1697189中所见,但是迄今为止,带有类型边界的 Map 的实现具有挑战性 .

  • 12

    使用implicits,类型类解决方案可能是最好的方法 . 这类似于Odersky / Spoon / Venners书中提到的monoid方法:

    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)
    

    如果你在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)
    
                  ^
    
  • 29

    还有这个黑客:

    implicit val x: Int = 0
    def foo(a: List[Int])(implicit ignore: Int) { }
    
    implicit val y = ""
    def foo(a: List[String])(implicit ignore: String) { }
    
    foo(1::2::Nil)
    foo("a"::"b"::Nil)
    

    Working around type erasure ambiguities (Scala) .

  • 0

    我们想要一个类型操作符 Or[U,V] ,它可以用于以 X <: UX <: V 的方式约束类型参数 X . 这是一个尽可能接近的定义:

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

    这是怎么回事用过的:

    // 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!
    

    这使用了一些Scala类型的技巧 . 主要是使用generalized type constraints . 给定类型 UV ,当且仅当Scala编译器能够证明 UV 的子类型时,Scala编译器才提供名为 U <:< V 的类(以及该类的隐式对象) . 这是一个使用通用类型约束的简单示例,适用于某些情况:

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

    此示例适用于 XB 的实例, String ,或者类型既不是超类型也不是 BString 的子类型 . 在前两种情况下, with 关键字 (B with String) <: B(B with String) <: String 的定义是正确的,因此Scala将提供一个将作为 ev 传入的隐式对象:Scala编译器将正确接受 foo[B]foo[String] .

    在最后一种情况下,我依赖的事实是,如果 U with V <: X ,那么 U <: XV <: X . 这看起来很直观,我只是假设它 . 从这个假设可以清楚地看出,当 XBString 的超类型或子类型时,为什么这个简单示例失败:例如,在上面的示例中, foo[A] 被错误地接受并且 foo[C] 被错误地拒绝 . 同样,我们想要的是变量 UVX 上的某种类型表达式,恰好在 X <: UX <: V 时为真 .

    斯卡拉的逆变概念在这里可以提供帮助 . 记住特质 trait Inv[-X] ?因为它的类型参数 XInv[X] <: Inv[Y] 是逆变的,当且仅当 Y <: X . 这意味着我们可以将上面的示例替换为实际可行的示例:

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

    那是因为表达式 (Inv[U] with Inv[V]) <: Inv[X] 是真的,通过上面的相同假设,恰好是 Inv[U] <: Inv[X]Inv[V] <: Inv[X] ,并且通过逆变的定义,这恰好是 X <: UX <: V .

    通过声明可参数化的类型 BOrString[X] 并使用它如下所示,可以使事情更加可重用:

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

    Scala现在将尝试为 X 调用 X 构造类型 BOrString[X] ,并且当 XBString 的子类型时,将精确构造类型 . 这有效,并且有一个简写符号 . 下面的语法是等效的(除了 ev 现在必须在方法体中引用为 implicitly[BOrString[X]] 而不是简单地 ev )并使用 BOrString 作为type context bound

    def foo[X : BOrString] = {}
    

    我们真正喜欢的是一种创建类型上下文绑定的灵活方法 . 类型上下文必须是可参数化的类型,我们需要一种可参数化的方法来创建一个 . 这听起来像我们试图在类型上讨论函数,就像我们在值上调整函数一样 . 换句话说,我们喜欢以下内容:

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

    这是Scala中的not directly possible,但我们可以使用一个技巧来达到非常接近 . 这将我们带到上面 Or 的定义:

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

    这里我们使用structural typing和Scala的pound operator创建一个结构类型 Or[U,T] ,保证有一个内部类型 . 这是一个奇怪的野兽 . 要给出一些上下文,必须使用 AnyRef 的子类调用函数 def bar[X <: { type Y = Int }](x : X) = {} ,其中定义了类型 Y

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

    使用pound运算符允许我们引用内部类型 Or[B, String]#pf ,并使用infix notation作为类型运算符 Or ,我们得到了 foo 的原始定义:

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

    我们可以使用函数类型在其第一个类型参数中是逆变的事实,以避免定义特征 Inv

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

    你可以看看MetaScala,它有一个叫OneOf的东西 . 我得到的印象是,这不适用于 match 语句,但您可以使用高阶函数模拟匹配 . 例如,看看this snippet,但请注意"simulated matching"部分已被注释掉,可能是因为它尚未完成 .

    现在进行一些编辑:我不会像你描述的那样对任何关于定义Either3,Either4等的东西感到震惊 . 这基本上是Scala内置的标准22元组类型的两倍 . 如果Scala有内置的析取类型,并且可能是一些很好的语法,如 {x, y, z} ,那肯定会很好 .

  • 37

    我认为第一类不相交类型是一个密封的超类型,具有备用子类型,并且隐式转换为/来自所需类型的这些替代亚型的分离 .

    我假设这个地址是comments 33 - 36英里萨林's solution, so the first class type that can be employed at the use site, but I didn' t测试它 .

    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
    

    一个问题是Scala不会在大小写匹配上下文中使用,从 IntOfIntOrStringInt (和 StringOfIntOrStringString )的隐式转换,因此必须定义提取器并使用 case Int(i) 而不是 case i : Int .


    ADD:我在他的博客上回复了Miles Sabin如下 . 也许有几个改进:

    • 它扩展到2种以上,在使用或定义网站上没有任何额外的噪音 .

    • 参数被隐式装箱,例如不需要 size(Left(2))size(Right("test")) .

    • 模式匹配的语法是隐式取消装箱的 .

    • 装箱和拆箱可能会被JVM热点优化掉 .

    • 语法可能是未来的第一类联合类型所采用的语法,因此迁移可能是无缝的?也许对于联合类型名称,最好使用 V 而不是 Or ,例如 IntVStringInt |v| StringInt or String,或者我最喜欢的Int|String


    更新:对上述模式的析取的逻辑否定如下,我added an alternative (and probably more useful) pattern at Miles Sabin's blog .

    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)
    

    另一个更新:关于Mile Sabin's solution的注释23和35,这里有一种在使用站点声明联合类型的方法 . 注意它在第一级之后是未装箱的,即它具有extensible to any number of types in the disjunction的优点,而 Either 需要嵌套装箱,而我之前的注释41中的范例不可扩展 . 换句话说, D[Int ∨ String] 可分配给 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]])
                    ^
    

    显然Scala编译器有三个错误 .

    • 在目标析取中的第一个类型之后,它不会为任何类型选择正确的隐式函数 .

    • 它不会从匹配中排除 D[¬[Double]] 案例 .

    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 {
                                               ^
    

    get方法不是't constrained properly on input type, because the compiler won' t允许 A 在协变位置 . 有人可能会认为这是一个错误,因为我们想要的只是证据,我们永远不会访问函数中的证据 . 我选择不在 get 方法中测试 case _ ,所以我不必在 size() 中的 match 中取消 Option .


    2012年3月5日:之前的更新需要改进 . Miles Sabin's solution正确处理子类型 .

    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]]
                     ^
    

    我之前更新的提议(近一流联盟类型)打破了子类型 .

    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)]]
                     ^
    

    问题是 (() => A) => A 中的 A 出现在协变(返回类型)和逆变(函数输入,或者在这种情况下是函数的返回值,它是函数输入)位置,因此替换只能是不变的 .

    注意 A => Nothing 是必要的只是因为我们想要 A 在逆变位置,所以 A are not subtypesD[¬[A]]D[¬[A] with ¬[U]]see also)的超类型 . 由于我们只需要双重逆处理,即使我们可以丢弃 ¬ ,我们也可以达到Miles的解决方案 .

    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]]]
                     ^
    

    所以完整的解决方案是 .

    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]
    }
    

    请注意,Scala中的前2个错误仍然存在,但是第3个错误被避免,因为 T 现在被约束为 A 的子类型 .

    我们可以确认子类型的工作原理 .

    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
    

    我一直认为第一类交集类型非常重要,对于reasons Ceylon has them,并且因为而不是subsumingAny 这意味着在预期类型上使用 match 取消装箱会产生运行时错误,所以取消装箱(heterogeneous collection包含a)析取可以进行类型检查(Scala必须修复我记录的错误) . 对于异构集合,联合比metascala的实验更为直接 .

  • 1

    如果你不理解库里 - 霍华德,还有另一种方法更容易理解:

    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
    

    我用的是technique in dijon

  • 166

    嗯,这一切都非常聪明,但我很确定你已经知道你的主要问题的答案是各种各样的“不” . Scala处理重载的方式不同,必须承认,比你描述的更不优雅 . 其中一些原因是由于Java互操作性,其中一些原因是由于不希望遇到类型推理算法的边缘情况,其中一些原因是它不是Haskell .

  • 8

    在这里添加已经很好的答案 . 这里's a gist that builds on Miles Sabin union types (and Josh'的想法)但也使它们递归定义,所以你可以在联合中有> 2种类型( def foo[A : UNil Or Int Or String Or List[String]

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

    注意:我应该补充一点,在玩了上面的项目之后,我最终回到了普通的旧和类型(即带有子类的密封特性) . Miles Sabin联合类型非常适合限制类型参数,但是如果你需要返回一个联合类型,那么它就不会提供太多 .

  • 13

    the docs开始,添加 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
    

    关于 sealed 部分:

    可以在程序的其他部分(...)中定义扩展类型Expr的其他案例类 . 可以通过声明基类Expr密封来排除这种可扩展性形式;在这种情况下,所有直接扩展Expr的类必须与Expr位于同一源文件中 .

相关问题