解决重载方法的双重定义的一种建议方法是用模式匹配代替重载:

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 楼

好吧,在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会在一种类型的伴随对象内部查看,以查看其中是否存在隐式内容,以使要求该类型的代码起作用。

评论


如果将StringOrInt [T]类设为密封,则至少在StringOrInt驻留在其中的情况下,将插入您所引用的“泄漏”(“当然,通过创建StringOrInt [Boolean]”,客户端代码可以绕过该泄漏)自己的文件。然后,必须在与StringOrInt相同的来源中定义见证对象。

–兰德尔·舒尔茨(Randall Schulz)
2010年8月18日,下午3:34

我试着对此解决方案进行一般化(在下面作为答案发布)。与Either方法相比,主要缺点似乎是我们失去了很多检查匹配的编译器支持。

–亚伦·诺夫斯特鲁普(Aaron Novstrup)
2010年8月18日在16:53

好招!但是,即使使用密封的类,您仍然可以通过在foo范围内定义隐式val b = new StringOrInt [Boolean]或通过显式调用foo(2.9)(new StringOrInt [Double])来在客户端代码中规避它。我认为您也需要使该类抽象。

– Paolo Falabella
2011年11月14日在8:59

是;最好使用特征StringOrInt ...

–机械蜗牛
2012年7月22日在21:32

P.s.如果要支持子类型,只需将StringOrInt [T]更改为StringOrInt [-T](请参见stackoverflow.com/questions/24387701/…)

–伊朗棉兰
2014年6月24日14:12



#2 楼

迈尔斯·萨宾(Miles Sabin)在他最近的博客文章中通过Curry-Howard同构在Scala中解开联合类型描述了一种非常好的获取联合类型的方法:

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

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
}


评论


那是我所见过的最棒的事情之一。

–次猴怪
2011-11-23 10:30

这是我对Miles想法的扩展实现:github.com/GenslerAppsPod/scalavro/blob/master/util/src/main/…–例如:github.com/GenslerAppsPod/scalavro/blob/master/util/src/测试/…

–康纳·道尔(Connor Doyle)
13年8月21日在19:40

以上评论应该是一个单独的答案。它只是Miles想法的一种实现,但是很好地包装在Maven Central上的一个程序包中,并且没有所有那些可能(?)对某些地方的构建过程造成问题的unicode符号。

–吉姆·皮瓦尔斯基(Jim Pivarski)
13年8月23日在15:01

这个有趣的角色是布尔否定。

–michid
15年3月2日在10:31

最初,这个想法对我来说太复杂了。阅读了该线程中提到的几乎每个链接,我被它的想法及其实现的美感所吸引:-) ...但是我仍然觉得这很令人费解...现在只是因为它尚不可用远离Scala。正如Miles所说:“现在,我们只需要缠扰Martin和Adriaan即可直接使用它。”

–理查德·戈麦斯(Richard Gomes)
15年3月29日在13:50



#3 楼

Dotty是一种新的实验性Scala编译器,支持联合类型(写为A | B),因此您可以执行所需的操作:

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


评论


这些日子中的一天。

–迈克尔·阿勒斯(Michael Ahlers)
18年5月11日在17:27

顺便说一句,Dotty将成为新的Scala 3(几个月前宣布)。

– 6infinity8
18年8月31日在10:49

并将在2020年末上市

– JulienD
19年8月14日在21:51

#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在Miles Sabin的这篇优秀博客文章下,它提供了另一种在Scala中编码联合类型的方式。 br />

评论


不幸的是,这种编码可能会失败:scala> f(9.2:AnyVal)通过类型检查器。

–基普顿·巴罗斯(Kipton Barros)
11年8月14日在6:20

@Kipton:很难过。 Miles Sabin的编码是否还会遭受此问题的困扰?

–missingfaktor
11年8月14日在17:35

Miles代码有一个稍微简单的版本;由于他实际上是在使用函数的逆参数的含义,而不是严格的“ not”,因此可以使用trait Contra [-A] {}代替所有函数。因此,您得到类似Union [A,B] = {类型的Check [Z] = Contra [Contra [Z]] <: i;情况s:字符串=> s.length}(无花式unicode)。

–雷克斯·克尔(Rex Kerr)
2011年8月23日23:00

这样可以解决联合类型的继承问题吗? stackoverflow.com/questions/45255270/…

– jhegedus
17年7月22日在13:59

嗯,我试过了,我无法使用此编码创建返回类型,因此似乎无法实现子类型stackoverflow.com/questions/45255270/…

– jhegedus
17年7月22日在14:10

#5 楼

可以将Daniel的解决方案概括如下:

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所指出的,它不处理混合类型的collections / varargs
如果匹配项不详尽,则编译器不会发出警告
如果匹配项包含不可能的情况,则编译器不会发出错误
对于Either方法,进一步的概括将需要定义类似的Or3Or4等特征。当然,定义此类特征比定义相应的Either类要简单得多。

更新:

Mitch Blevins演示了一种非常相似的方法,并展示了如何将其推广到更多领域。两种类型,称为“口吃或”。

#6 楼

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

Given ¬[-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的TList所见,但是到目前为止,实现Map带有类型限制的实现被证明是具有挑战性的。

评论


太好了,谢谢!我尝试了较早的方法,但在将泛型类型用作联合的一部分时始终遇到问题。这是我可以使用泛型类型的唯一实现。

– Samer Adra
16年15月15日在22:15

令人遗憾的是,但是可能是意料之中的,当我尝试使用Scala方法从Java代码中获取联合类型时,它不起作用。错误:(40,29)java:类Config中的方法setValue不能应用于给定类型;必需:X,scala.Predef。$ less $ colon $ less ,UnionTypes.package。$ u00AC >找到:java.lang.String原因:无法推断类型变量X(实际和形式参数列表的长度不同)

– Samer Adra
16/12/16在5:32



在此实现中的某些细节上还不太清楚。例如,原始文章将否定定义为“类型¬[A] = A =>没有”,但在此版本中,如果只是具有“密封特征¬[-A]”并且该特征未扩展到任何地方。这是如何运作的?

– Samer Adra
16 Dec 16'在16:21

@Samer Adra无论哪种方式都可以工作,本文使用Function1作为现有的逆类型。您不需要实现,只需要符合性证明(<:<)。

– J Cracknell
16/12/16在17:17



任何想法如何有一个接受联合类型的构造函数?

– Samer Adra
17年3月17日在18:20

#7 楼

类型类解决方案可能是使用隐式方法的最好方法。
这类似于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中运行此程序:

评论


我可能是错的,但是我不认为这是OP所寻找的。 OP询问的是可能表示类型的不相交并集的数据类型,然后在运行时对其进行案例分析,以查看实际类型是什么。类型类不能解决这个问题,因为它们是纯编译时构造的。

–汤姆·克罗基特(Tom Crockett)
10年8月20日在22:27

真正要问的问题是如何针对不同的类型公开不同的行为,但又不要过载。如果没有类型类的知识(也许对C / C ++有所了解),则联合类型似乎是唯一的解决方案。 Scala先前存在的Either类型倾向于加强这种信念。通过Scala的隐式类型使用类型类是解决底层问题的更好方法,但这是一个相对较新的概念,并且尚未广为人知,这就是为什么OP甚至不知道将它们视为联合类型的可能替代方法的原因。

–凯文·赖特(Kevin Wright)
2010年8月23日在11:46



这可以与子类型化一起使用吗? stackoverflow.com/questions/45255270/…

– jhegedus
17年7月22日在13:53

#8 楼

我们想要一个类型运算符Or[U,V],它可以用于约束类型参数X,从而使X <: UX <: 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类型的技巧。主要的一种是使用广义类型约束。给定类型UV,并且仅当Scala编译器可以证明U <:< VU的子类型时,Scala编译器才提供一个名为V的类(以及该类的隐式对象)。这是一个使用通用类型约束的简单示例,在某些情况下可以使用:

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


XB类的实例,String或类型为既不是BString的超型也不是其子类型。在前两种情况下,根据with(B with String) <: B的关键字定义确实如此,因此Scala将提供一个隐式对象,该对象将作为(B with String) <: String传递:Scala编译器将正确接受evfoo[B]

在最后一种情况下,我所依赖的事实是,如果foo[String]U with V <: XU <: X。从直觉上看,这是正确的,我只是假设是这样。从这个假设可以清楚地看到,当V <: XXB的超类型或子类型时,为什么这个简单示例失败:例如,在上面的示例中,String被错误接受,而foo[A]被错误拒绝。同样,我们想要的是在变量foo[C]UV上的某种类型表达式,这在XX <: U时才正确。

Scala的逆向性概念可以为您提供帮助。还记得X <: V的特征吗?因为它的类型参数trait Inv[-X]是互变的,所以qq12020q仅当X时才是Inv[X] <: Inv[Y]。这意味着我们可以将上面的示例替换为一个实际可用的示例:

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


这是因为表达式Y <: X是正确的(根据上述相同的假设),恰好是(Inv[U] with Inv[V]) <: Inv[X]Inv[U] <: Inv[X],并通过反差的定义,这恰好在Inv[V] <: Inv[X]X <: U时成立。

通过声明可参数化的类型X <: V并按如下方式使用,可以使事情变得更可重用: />
trait Inv[-X]
type BOrString[X] = (Inv[B] with Inv[String]) <:< Inv[X]
def foo[X](implicit ev : BOrString[X]) = {}


现在,Scala将尝试为调用BOrString[X]的每个BOrString[X]构造类型X,并且当fooXB的子类型时,将精确构造该类型。 。那行得通,并且有一个简写形式。下面的语法是等效的(但现在必须在方法主体中将String引用为ev而不是简单地implicitly[BOrString[X]]),并使用ev作为类型上下文绑定:

def foo[X : BOrString] = {}


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

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


在Scala中不可能直接实现,但是有一个技巧可以使我们变得更近。这使我们对上面的BOrString进行了定义:

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


在这里,我们使用结构化类型和Scala的英镑运算符来创建结构化类型Or,该结构化类型保证具有一个内部类型。这是一只奇怪的野兽。为了提供一些上下文信息,必须使用在其中定义了Or[U,T]类型的def bar[X <: { type Y = Int }](x : X) = {}子类调用函数AnyRef

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


使用英镑运算符可以使我们引用内部类型Y,并对类型运算符Or[B, String]#pf使用中缀表示法,我们得出Or的原始定义:

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


我们可以使用以下事实:为了避免定义特征而在其第一类型参数中使用了变体:

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


评论


这样可以解决A | B <:A | B | C问题吗? stackoverflow.com/questions/45255270 / ...我不能告诉。

– jhegedus
17年7月22日在14:27

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


请参阅解决类型擦除歧义(Scala)。

评论


参见stackoverflow.com/questions/3422336/…。实际上有一个更简单的技巧:只需将(隐式e:DummyImplicit)添加到一种类型签名中即可。

–亚伦·诺夫斯特鲁普(Aaron Novstrup)
2010年8月20日在19:17

#10 楼

您可以看一下MetaScala,它具有一个名为OneOf的东西。我觉得这不适用于match语句,但是您可以使用高阶函数来模拟匹配。例如,查看一下此代码段,但请注意,“模拟匹配”部分已被注释掉,可能是因为它尚未完全起作用。

现在进行一些编辑:我不认为您所描述的定义Either3,Either4等有什么惊人的事情。这基本上是内置于Scala的标准22元组类型的双重形式。如果Scala具有内置的析取类型,那肯定会很好,也许像{x, y, z}这样的语法也很不错。

#11 楼

我认为第一类不相交类型是密封的超类型,具有备用子类型,并且隐含转换到或从所需的分离形式转换为这些备用子类型。

我认为这可以解决评论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)而不是case i : Int


添加:我对Miles Sabin的博客回复如下。也许对Either进行了一些改进:


它扩展到2种以上的类型,在使用或定义站点上没有任何其他噪音。
参数被隐式装箱,例如不需要size(Left(2))size(Right("test"))
模式匹配的语法被隐式取消装箱。
装箱和拆箱可以通过JVM热点进行优化。
语法可以是采用的一种未来的头等工会类型,那么迁移也许是无缝的?也许对于联合类型名称,最好使用V而不是Or,例如IntVStringInt |v| StringInt or String或我最喜欢的Int|String吗?


更新:上述模式的逻辑取反之后,我添加了另一种选择(可能更有用的方法)在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)
 



另一个更新:关于Mile Sabin解决方案的注释23和35,这是在使用站点声明联合类型的一种方法。请注意,在第一级之后将其取消装箱,即,其优势是可扩展为任意数量的类型,而Either需要嵌套装箱,而我在先前的评论41中的范式不可扩展。换句话说,D[Int ∨ String]可分配给D[Int ∨ String ∨ Double](即为D[¬[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编译器存在三个错误。


它将不会在目标析取中的第一个类型之后为任何类型选择正确的隐式函数。
它不会从匹配项中排除A的情况。

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方法未正确限制在输入类型上,因为编译器不允许case _在协变位置。有人可能会认为这是一个错误,因为我们想要的只是证据,而我们从不访问函数中的证据。而且我选择了不使用get方法测试Option,因此我不必拆开matchsize()中的A装箱。


2012年3月5日:更新需要改进。 Miles Sabin的解决方案可以正确地进行子类型化。

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 => Nothing出现在协变(返回类型)和反变量(函数输入,或者在这种情况下是函数输入的返回值)位置中,因此替换可以只是不变的。

请注意,仅因为我们希望A处于反位置,才需要A,因此D[¬[A]]的超类型既不是D[¬[A] with ¬[U]]的子类型也不是¬的子类型(另请参见)。因为我们只需要双重协方差,所以即使我们可以丢弃T,我们也可以实现与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个bug仍然存在,但避免了第3个bug,因为现在A已被约束为Any的子类型。

我们可以确认子类型化的工作。

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


我一直认为一流的交叉点类型非常重要,这既是因为锡兰拥有它们,也是因为而不是包含在内到match,这意味着对期望类型的q4312079q进行拆箱会生成运行时错误,可以对(分离的包含a的)集合进行拆箱的类型进行检查(Scala必须修复我提到的错误)。与使用异规模集合的实验性Metascala HList的复杂性相比,并集要简单得多。

评论


上面的#3项不是Scala编译器中的错误。请注意,我最初没有将其编号为错误,然后今天不小心进行了编辑,然后进行了编辑(忘记了我最初未声明它是错误的原因)。我没有再编辑帖子,因为我处于7个编辑限制。

–谢尔比·摩尔三世(Shelby Moore III)
2012年5月5日19:28

使用大小函数的不同公式可以避免上述#1错误。

–谢尔比·摩尔三世(Shelby Moore III)
2012年5月3日14:19

#2项不是错误。 Scala无法完全表达联合类型。链接的文档提供了另一个版本的代码,因此大小不再接受D [Any]作为输入。

–谢尔比·摩尔三世(Shelby Moore III)
2012年5月4日,9:27

我不太明白这个答案,这也是这个问题的答案:stackoverflow.com/questions/45255270/…

– jhegedus
17年7月22日在14:14

#12 楼

如果您不喜欢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


我在第戎中使用了类似的技术

评论


可以使用子类型化吗?我的直觉是:不,但我可能错了。 stackoverflow.com/questions/45255270/…

– jhegedus
17年7月22日在14:16

#13 楼

好吧,那都很聪明,但是我敢肯定,您已经知道,您提出的主要问题的答案是各种各样的“否”。 Scala处理超载的方式有所不同,必须承认,它比您描述的要优雅一些。其中一些是由于Java的互操作性,一些是由于不希望碰到类型推断算法的极端情况,而某些则是由于它不是Haskell。

评论


虽然我已经使用Scala一段时间了,但我并不像您认为的那样知识渊博,也不聪明。在此示例中,我可以看到库如何提供解决方案。然后想知道是否存在这样的库(或某些替代方法)是有意义的。

–亚伦·诺夫斯特鲁普(Aaron Novstrup)
2010年8月18日,下午2:05

#14 楼

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

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

NB:我应该补充一点,在为项目完成上述工作之后,我最终回到了简单的旧和类型(即具有子类的密封特征) 。 Miles Sabin联合类型非常适合用于限制type参数,但是如果您需要返回一个联合类型,那么它并不能提供太多的功能。

评论


这样可以解决A | C <:A | B | C子类型化问题吗? stackoverflow.com/questions/45255270/…我的直觉是“不”,因为那意味着A或C必须是(A或B)或C的子类型,但不包含A或C类型,所以没有希望至少使用这种编码使A或C成为A或B或C的子类型...您怎么看?

– jhegedus
17年7月22日在14:30



#15 楼

从文档中添加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的case类(...)。可以通过声明密封的基类Expr来排除这种形式的可扩展性。在这种情况下,所有直接扩展Expr的类都必须与Expr在同一个源文件中。


#16 楼

在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