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

Posted

技术标签:

【中文标题】如何定义“类型析取”(联合类型)?【英文标题】:How to define "type disjunction" (union types)? 【发布时间】:2011-03-31 07:13:21 【问题描述】:

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 没有内置的“类型析取”。另外,上面定义的隐式转换是否潜伏在标准库中的某个地方,以便我可以导入它们?

【问题讨论】:

【参考方案1】:

Miles Sabin 在他最近的博文Unboxed union types in Scala via the Curry-Howard isomorphism 中描述了一种非常好的获取联合类型的方法:

他首先将类型的否定定义为

type ¬[A] = A => Nothing

使用德摩根定律,这允许他定义联合类型

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

【讨论】:

这是我见过的最棒的事情之一。 这是我对 Miles 想法的扩展实现:github.com/GenslerAppsPod/scalavro/blob/master/util/src/main/… -- 示例:github.com/GenslerAppsPod/scalavro/blob/master/util/src/test/… 以上评论本身应该是一个答案。它只是 Miles 想法的一个实现,但是很好地封装在 Maven Central 上的一个包中,并且没有所有那些可能(?)在某个构建过程中对某些东西造成问题的 unicode 符号。 那个有趣的角色是boolean negation。 最初,这个想法对我来说太复杂了。阅读了这个线程中提到的几乎每个链接,我被这个想法和它的实现之美所吸引:-)......但我仍然觉得这是一件令人费解的事情......现在只是因为它还没有直接可用远离斯卡拉。正如 Miles 所说:“现在我们只需要纠缠 Martin 和 Adriaan 就可以直接访问它。”【参考方案2】:

好吧,在 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 所述,您将StringOrInt 设为sealed 类。

之所以有效,是因为T: StringOrInt 意味着有一个 StringOrInt[T] 类型的隐式参数,并且因为 Scala 会在一个类型的伴随对象内部查看是否存在隐式以使请求该类型的代码工作。

【讨论】:

如果将class StringOrInt[T] 设为sealed,则您提到的“泄漏”(“当然,客户端代码可以通过创建StringOrInt[Boolean]”来避开) ,至少如果StringOrInt 驻留在它自己的文件中。那么见证对象必须定义在与StringOrInt相同的源中。 我尝试对这个解决方案进行一些概括(在下面作为答案发布)。与Either 方法相比,主要缺点似乎是我们失去了很多编译器对检查匹配的支持。 好把戏!但是,即使使用密封类,您仍然可以通过在 foo 范围内定义隐式 val b = new StringOrInt[Boolean] 或通过显式调用 foo(2.9)(new StringOrInt[Double]) 在客户端代码中规避它。我认为您也需要将类抽象化。 是的;使用trait StringOrInt ... 可能会更好 附言。如果您想支持子类型,只需将StringOrInt[T] 更改为StringOrInt[-T](参见***.com/questions/24387701/…)【参考方案3】:

Dotty,一个新的实验性 Scala 编译器,支持联合类型(写成A | B),所以你可以做你想做的事:

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

【讨论】:

这些天之一。 顺便说一下,Dotty 将是新的 scala 3(几个月前宣布的)。 将于 2020 年底推出【参考方案4】:

这是对联合类型进行编码的 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)
        ^

来源:评论 #27 在 this 下由 Miles Sabin 撰写的优秀博文,它提供了在 Scala 中编码联合类型的另一种方式。

【讨论】:

不幸的是,这种编码可以被击败:scala&gt; f(9.2: AnyVal) 通过了类型检查器。 @Kipton:这很可悲。 Miles Sabin 的编码是否也存在这个问题? Miles 的代码有一个稍微简单的版本;由于他实际上使用的是函数逆变参数的反向含义,而不是严格的“不”,因此您可以使用trait Contra[-A] 代替所有函数。所以你会得到像 type Union[A,B] = type Check[Z] = Contra[Contra[Z]] &lt;:&lt; Contra[Contra[A] with Contra[B]] 这样的东西,像 def f[T: Union[Int, String]#Check](t: T) = t match case i: Int =&gt; i; case s: String =&gt; s.length 一样使用(没有花哨的 unicode)。 这可能解决联合类型的继承问题? ***.com/questions/45255270/… 嗯,我试过了,我不能用这种编码创建返回类型,所以它似乎无法实现子类型***.com/questions/45255270/…【参考方案5】:

可以将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")
   

这种方法的主要缺点是

正如 Daniel 指出的那样,它不处理具有混合类型的集合/可变参数 如果匹配不完整,编译器不会发出警告 如果匹配包含不可能的情况,编译器不会发出错误 与Either 方法一样,进一步泛化需要定义类似的Or3Or4 等特征。当然,定义此类特征会比定义相应的 Either 类要简单得多。

更新:

Mitch Blevins demonstrates a very similar approach 并展示了如何将其概括为两种以上的类型,将其称为“口吃或”。

【讨论】:

【参考方案6】:

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

给定类型¬[-A],它在A上是逆变的,根据给定A &lt;: B的定义,我们可以写 ¬[B] &lt;: ¬[A],反转类型的顺序。

给定类型ABX,我们想要表达X &lt;: A || X &lt;: B。 应用逆变,我们得到¬[A] &lt;: ¬[X] || ¬[B] &lt;: ¬[X]。这又可以 表示为¬[A] with ¬[B] &lt;: ¬[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 的TLists 中看到的成员类型上限结合起来,但是到目前为止,使用类型边界实现Map 具有挑战性。

【讨论】:

这太棒了,谢谢!我尝试了早期的方法,但在使用泛型类型作为联合的一部分时一直遇到问题。这是我可以使用泛型类型的唯一实现。 很遗憾,但可能是意料之中的,当我尝试使用从 Java 代码中获取联合类型的 Scala 方法时,它不起作用。错误:(40, 29) java: 类 Config 中的方法 setValue 不能应用于给定类型;必需:X,scala.Predef.$less$colon$less,UnionTypes.package.$u00AC> 找到:java.lang.String 原因:无法推断类型变量 X(实际参数列表和形式参数列表的长度不同) 对这个实现中的一些细节还不是很清楚。例如,原始文章将否定定义为“type ¬[A] = A => Nothing”,但在这个版本中,如果只有“sealed trait ¬[-A]”并且该特征没有在任何地方扩展。这是如何工作的? @Samer Adra 无论哪种方式都可以,文章使用Function1 作为现有的逆变类型。您不需要实现,只需要符合性证明 (&lt;:&lt;)。 知道如何拥有一个接受联合类型的构造函数吗?【参考方案7】:

类型类解决方案可能是最好的方法,使用隐式。 这类似于 Odersky/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)

如果你随后在 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)

              ^

【讨论】:

我可能是错的,但我认为这不是 OP 想要的。 OP 正在询问一种可以表示不相交的类型联合的数据类型,然后对其进行案例分析在运行时以查看实际类型是什么。类型类不能解决这个问题,因为它们是纯粹的编译时构造。 被问到的真正的问题是如何为不同类型公开不同的行为,但又不会重载。如果不了解类型类(并且可能对 C/C++ 有所了解),联合类型似乎是唯一的解决方案。 Scala 预先存在的Either 类型倾向于强化这种信念。通过 Scala 的隐式使用类型类是解决潜在问题的更好方法,但它是一个相对较新的概念,仍未广为人知,这就是为什么 OP 甚至不知道将它们视为联合类型的可能替代方案。 这对子类型有效吗? ***.com/questions/45255270/…【参考方案8】:

我们想要一个类型运算符Or[U,V],它可以用来约束类型参数X,使得X &lt;: UX &lt;: V。这是一个尽可能接近的定义:

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 &lt;:&lt; V 的类(以及该类的隐式对象)。这是一个使用适用于某些情况的通用类型约束的更简单示例:

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

X 是类B 的实例、String 或具有既不是BString 的超类型也不是子类型的类型时,此示例有效。在前两种情况下,通过 with 关键字的定义,(B with String) &lt;: B(B with String) &lt;: String 是正确的,因此 Scala 将提供一个隐式对象,该对象将作为 ev 传入:Scala 编译器将正确接受 @ 987654349@ 和foo[String]

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

Scala 的逆变概念在这里可以提供帮助。还记得trait Inv[-X] 的特征吗?因为它的类型参数XInv[X] &lt;: Inv[Y] 是逆变的当且仅当Y &lt;: X。这意味着我们可以将上面的示例替换为实际可行的示例:

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

这是因为表达式(Inv[U] with Inv[V]) &lt;: Inv[X] 为真,根据上述相同假设,恰好在Inv[U] &lt;: Inv[X]Inv[V] &lt;: Inv[X] 时,并且根据逆变的定义,这恰好在X &lt;: UX &lt;: V 时为真。

通过声明一个可参数化的类型BOrString[X] 并按如下方式使用它,可以让事情变得更加可重用:

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

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

def foo[X : BOrString] = 

我们真正想要的是一种灵活的方式来创建类型上下文绑定。类型上下文必须是可参数化的类型,我们需要一种可参数化的方式来创建它。这听起来就像我们在尝试对类型进行 curry 函数,就像我们对值进行 curry 函数一样。换句话说,我们想要如下内容:

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 &lt;: type Y = Int ](x : X) = ,这些子类中定义了 Y 类型:

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

使用磅运算符允许我们引用内部类型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 => _)
 

【讨论】:

这可以解决A|B &lt;: A|B|C 的问题吗? ***.com/questions/45255270/…我不知道。【参考方案9】:

还有这个hack

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)

【讨论】:

见***.com/questions/3422336/…。实际上有一个更简单的技巧:只需将 (implicit e: DummyImplicit) 添加到类型签名之一。【参考方案10】:

你可以看看MetaScala,它有一个叫做OneOf的东西。我的印象是这不适用于match 语句,但您可以使用高阶函数模拟匹配。以this snippet 为例,但请注意“模拟匹配”部分已被注释掉,可能是因为它还不能正常工作。

现在进行一些社论:我不认为像您描述的那样定义 Either3、Either4 等有什么过分的地方。这与 Scala 内置的标准 22 元组类型本质上是双重的。如果 Scala 有内置的析取类型当然会很好,也许还有一些不错的语法,比如x, y, z

【讨论】:

【参考方案11】:

我认为第一类不相交类型是一个密封的超类型,具有备用子类型,以及与这些替代子类型的所需分离类型的隐式转换。

我假设这个地址comments 33 - Miles Sabin 的解决方案的 36 个,所以是可以在使用现场使用的第一类类型,但我没有测试它。

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) 而不是 @987654344 @。


添加:我在他的博客中对 Miles Sabin 的回复如下。或许对 Either 有一些改进:

    它扩展到2种以上,在使用或定义现场没有任何额外的噪音。 参数被隐式装箱,例如不需要size(Left(2))size(Right("test"))。 模式匹配的语法被隐式拆箱。 装箱和拆箱可能会被 JVM 热点优化掉。 该语法可能是未来一流联合类型采用的语法,因此迁移可能是无缝的?也许对于联合类型名称,最好使用V 而不是Or,例如IntVString、`Int |v| String`、`Int 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 的 cmets 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 方法没有正确限制输入类型,因为编译器不允许A 在协变位置。有人可能会争辩说这是一个错误,因为我们想要的只是证据,我们永远不会访问函数中的证据。我选择不在get 方法中测试case _,所以我不必在match 中的size() 中拆箱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)]]
                 ^

问题是(() =&gt; A) =&gt; A中的A同时出现在协变(返回类型)和逆变(函数输入,或者在这种情况下是作为函数输入的函数的返回值)位置,因此替换只能保持不变。

请注意,A =&gt; Nothing 是必需的,因为我们希望 A 处于逆变位置,因此 A 的超类型 are not subtypes 的 D[¬[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,而且因为不是subsuming 到Any 这意味着在预期类型上使用match 取消装箱可以生成运行时错误,(heterogeneous collection 包含一个)析取的拆箱可以进行类型检查(Scala 必须修复我注意到的错误)。对于异构集合,联合比 complexity of using 和 metascala 的实验性 HList 更直接。

【讨论】:

上面的#3 是not a bug in the Scala compiler。请注意,我最初没有将其编号为错误,然后今天不小心进行了编辑并这样做了(忘记了我最初没有说明它是错误的原因)。我没有再次编辑帖子,因为我有 7 次编辑限制。 使用different formulation of the size function 可以避免上述#1 错误。 #2 项目是not a bug. Scala can't fully express a union type。链接文档提供了另一个版本的代码,因此size 不再接受D[Any] 作为输入。 我不太明白这个答案,这也是这个问题的答案吗:***.com/questions/45255270/…【参考方案12】:

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

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

【讨论】:

这可以与子类型一起使用吗?我的直觉:不,但我可能错了。 ***.com/questions/45255270/…【参考方案13】:

嗯,这一切都很聪明,但我很确定你已经知道你的主要问题的答案是各种“不”。 Scala 以不同的方式处理重载,并且必须承认,它没有您描述的那么优雅。其中一些是由于 Java 互操作性,其中一些是由于不想遇到类型推断算法的边缘情况,还有一些是因为它根本不是 Haskell。

【讨论】:

虽然我使用 Scala 已经有一段时间了,但我并不像你想象的那样知识渊博,也没有你想象的那么聪明。在这个例子中,我可以看到图书馆如何提供解决方案。然后怀疑这样的库是否存在(或其他替代方案)是有道理的。【参考方案14】:

在这里添加已经很好的答案。这是一个基于 Miles Sabin 联合类型(和 Josh 的想法)的要点,但也使它们递归定义,因此您可以在联合中拥有 >2 种类型 (def foo[A : UNil Or Int Or String Or List[String])

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

注意:我应该补充一点,在玩了上述项目之后,我最终回到了普通的旧总和类型(即带有子类的密封特征)。 Miles Sabin 联合类型非常适合限制类型参数,但如果您需要返回联合类型,那么它提供的功能不多。

【讨论】:

这能解决A|C &lt;: A|B|C子类型问题吗? ***.com/questions/45255270/… 我的直觉是 NO,因为那意味着 A or C 需要成为 (A or B) or C 的子类型,但它不包含 A or C 类型,因此没有希望将 A or C 设为 @ 的子类型987654329@至少用这个编码...你觉得呢?【参考方案15】:

在 Scala 3 中,您可以使用联合类型 启动 Scala 3 项目:https://dotty.epfl.ch/#getting-started

一种方法是

sbt new lampepfl/dotty.g8

然后您可以将目录更改为项目,然后键入 sbt console 以启动 REPL。

参考:https://dotty.epfl.ch/docs/reference/new-types/union-types.html

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

scala> foo(2,"2","acc",-223)                                                    
int
str
str
int

【讨论】:

【参考方案16】:

来自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 在同一个源文件中。

【讨论】:

以上是关于如何定义“类型析取”(联合类型)?的主要内容,如果未能解决你的问题,请参考以下文章

自定义结构类型:结构体枚举联合

C语言进阶学习笔记四自定义类型(枚举+联合)

如何从打字稿中的标记联合类型中提取类型?

C语言进阶自定义类型详解(结构体+枚举+联合)

c语言 联合和枚举

c语言 联合和枚举