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


181

已经一个方法被提出来处理的重载方法双定义是,以取代与模式匹配超载:

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

Answers:


142

好吧,在的特定情况下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],除非像由著名兰德尔下面,你做StringOrInt一个sealed类。

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


14
如果class StringOrInt[T]进行了sealed,则插入了您所引用的“泄漏”(“当然,可以通过创建StringOrInt[Boolean]“ 来使客户端代码避开此泄漏),至少如果StringOrInt驻留在其自己的文件中。然后,必须在相同的来源中定义见证对象StringOrInt
兰德尔·舒尔茨

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

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

2
是; 它可能会更好地使用trait StringOrInt ...
机械蜗牛

7
附言:如果你想支持亚型简单地改变StringOrInt[T]StringOrInt[-T](见stackoverflow.com/questions/24387701/...
伊兰棉兰

178

迈尔斯·萨宾(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
}

13
那是我所见过的最棒的事情之一。
Submonoid 2011年


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

2
那可笑的性格是布尔否定
michid 2015年

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

44

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

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

1
这些日子中的一天。
迈克尔·阿勒斯

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

1
并将于后期2020可以拿到
JulienD

31

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

资料来源: Miles Sabin 在这篇出色的博客文章下的第27条评论,它提供了另一种在Scala中编码联合类型的方法。


6
不幸的是,这种编码可能会失败:scala> f(9.2: AnyVal)通过类型检查器。
基普顿·巴罗斯

@Kipton:真可悲。Miles Sabin的编码是否还会遇到此问题?
missingfaktor 2011年

9
Miles代码有一个稍微简单的版本;由于他实际上是在使用函数的逆参数的含义,而不是严格的“ not”,因此可以trait Contra[-A] {}代替所有函数使用。因此,您会得到类似type Union[A,B] = { type Check[Z] = Contra[Contra[Z]] <:< Contra[Contra[A] with Contra[B]] }二手的东西def f[T: Union[Int, String]#Check](t: T) = t match { case i: Int => i; case s: String => s.length }(没有花哨的unicode)。
Rex Kerr

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

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

18

可以将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指出的那样,它不处理混合类型的collection / varargs
  • 如果匹配不完全,则编译器不会发出警告
  • 如果匹配包含不可能的情况,则编译器不会发出错误
  • Either方法,进一步推广需要定义类似Or3Or4等特点。当然,定义这些特征比定义相应的Either类要简单得多。

更新:

Mitch Blevins 演示 了一种非常相似的方法,并展示了如何将其归纳为两种以上的类型,称其为“口吃或”。


17

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

给定类型¬[-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 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
}

我确实花了一些时间尝试将这种想法与成员类型的上限结合起来TListharrah / up s所示,但是Map迄今为止,实现具有类型界限的方法被证明具有挑战性。


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

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

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

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

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

12

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

              ^

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

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

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

10

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

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

Xclass B,a String或class的实例既不是Bor 的超类型也不是其子类型的类型时,此示例适用String。在前两种情况下,with关键字(B with String) <: Band和的定义是正确的(B with String) <: String,因此Scala将提供一个隐式对象,该对象将作为传递ev:Scala编译器将正确接受foo[B] and foo[String]

在最后一种情况下,我依赖于if U with V <: X,then U <: XV <: X。从直觉上看,这是正确的,我只是假设它是。为什么当这个简单的例子失败这是一个从这个假设明确X是的超类型或亚型要么BString:例如,在上面的例子中,foo[A]被错误地接受,foo[C]被错误地拒绝。同样,我们想要的是变量U,上的某种类型表达式V,而XX <: UX <: V

Scala的逆向性概念在这里可以有所帮助。还记得特质trait Inv[-X]吗?因为它的类型参数是反变的X,所以Inv[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]时为true,并且根据反差的定义,在X <: U或时也为true X <: V

通过声明可参数化的类型BOrString[X]并按如下方式使用它,可以使事情更具可重用性:

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

斯卡拉现在将尝试构建类型BOrString[X]为每一个Xfoo被调用,当类型将被精确构造X是无论是亚型BString。那行得通,并且有一个简写形式。下面的语法是等效的(除了ev现在必须在方法主体中引用implicitly[BOrString[X]]而不是简单引用ev),并将其BOrString用作类型上下文绑定

def foo[X : BOrString] = {}

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

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

在Scala中这不可能直接实现,但是有一个技巧可以使我们接近。这将我们带到Or上面的定义:

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

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

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

使用磅运算符允许我们引用内部类型Or[B, String]#pf,对类型运算符使用中缀表示法Or,我们得出以下原始定义foo

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

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

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

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


7

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

现在进行一些编辑:我认为您所描述的定义Either3,Either4等并没有什么可怕的事情。这基本上是内置于Scala的标准22元组类型的两倍。如果Scala具有内置的析取类型,那可能会很好,并且可能对它们有一些不错的语法,例如{x, y, z}


6

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

我认为这解决了Miles Sabin解决方案的第33-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不会在大小写匹配的上下文中使用,即从IntOfIntOrStringto Int(和StringOfIntOrStringto String)的隐式转换,因此必须定义提取器并使用case Int(i)代替case i : Int


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

  1. 它扩展到两种以上的类型,在使用或定义站点上没有任何其他噪音。
  2. 参数被隐式装箱,例如,不需要size(Left(2))size(Right("test"))
  3. 模式匹配的语法隐式取消装箱。
  4. 装箱和拆箱可以通过JVM热点进行优化。
  5. 该语法可能是将来的一流联合类型采用的语法,因此迁移可能是无缝的?也许对于联合体类型名称,最好使用V代替Or,例如IntVString` Int |v| String`,` Int 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中的范式则不可扩展。换句话说,a D[Int ∨ String]可分配给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]])
                ^

显然,Scala编译器存在三个错误。

  1. 在目标析取中的第一个类型之后,它将不会为任何类型选择正确的隐式函数。
  2. 它不会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在协变位置上使用。有人可能会认为这是一个错误,因为我们想要的只是证据,而我们从不访问函数中的证据。我做出的选择不测试case _get方法,所以我不会有拆箱一Optionmatchsize()


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

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

我先前的更新建议(针对近乎一流的联合体类型)打破了子类型化。

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

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

请注意,A => Nothing仅仅是因为我们要的是需要A在逆变位置,这样的超类型A 不亚型D[¬[A]]也不是D[¬[A] with ¬[U]]另见)。由于我们只需要双重逆方差,即使我们可以丢弃¬和,我们也可以实现与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,因为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

我一直在想的是一流的路口类型是非常重要的,无论对于原因锡兰有他们,因为不是归并Any该装置与拆箱match预期的类型可以产生一个运行时错误,拆箱一(的均质集合体含a)分离可以进行类型检查(Scala必须修复我指出的错误)。工会比更直接使用的复杂实验HListmetascala异构集合。


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

可以通过使用不同的size函数公式避免上述#1错误。
谢尔比·摩尔

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

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

5

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

1

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


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

1

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

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

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


这样可以解决A|C <: A|B|C子类型问题吗?stackoverflow.com/questions/45255270/… 我的直觉是“不”,因为那将意味着它A or C必须是的子类型,(A or B) or C但不包含该类型,A or C因此至少没有希望使用这种编码来制作A or C子类型A or B or C。 。 你怎么看 ?
jhegedus

0

文档中,添加以下内容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在同一源文件中。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.