在Scala中,如何定义在运行时工作的联合类型?

Ais*_*ish 3 functional-programming scala union-types

接下来就如何在Scala中定义联合类型形成了这一组优秀的答案.我一直在使用Miles Sabin对Union类型的定义,但仍有一个问题.

如果直到运行时才知道类型,你如何使用这些?例如:

trait inv[-A] {}
type Or[A,B] = {
  type check[X] = (inv[A] with inv[B]) <:< inv[X]
}

case class Foo[A : (Int Or String)#check](a: A)

Foo(1)    // Foo[Int] = Foo(1)
Foo("hi") // Foo[String] = Foo(hi)
Foo(2.0)  // Error!
Run Code Online (Sandbox Code Playgroud)

此示例有效,因为参数A在编译时已知,并且调用Foo(1)实际上正在调用Foo[Int](1).但是,如果A在运行时之前不知道参数,您会怎么做?也许你正在削减包含Foos 的数据的文件,在这种情况下,在Foo读取数据之前不知道类型参数.A在这种情况下,没有简单的方法来设置参数.

我能够提出的最佳解决方案是:

  • 模式匹配您已阅读的数据,然后Foo根据该类型创建不同的数据.在我的情况下,这是不可行的,因为我的case-class实际上包含了几十个union类型,因此有数百种类型的组合来模式匹配.

  • 转换您刚读过的类型(String or Int),因此您只能传递一种类型,在Foo使用它时传递Type Class约束.然后返回Foo[_].这使得Foo用户有责任计算出每个字段的类型(因为它们看起来都是Any类型),但至少在实际使用字段之前它必须知道类型,在这种情况下,模式匹配似乎更容易处理.

第二个解决方案如下所示:

def parseLine: Any // Parses data point, but can be either a String or 
                   // Int, so returns Any.

def mkFoo: Foo[_] = {
  val a = parseLine.asInstanceOf[Int with String]
  Foo(a) // Passes type constraint now
}
Run Code Online (Sandbox Code Playgroud)

在实践中我最终使用了第二种解决方案,但我想知道我能做些什么更好的事情?

说明问题的另一种方法是:返回联盟类型是什么意思?函数只能返回一个类型,我们与Miles Sabin联合类型一起使用的技巧仅对您传入的类型有用,而不适用于您返回的类型.

PS.对于上下文,为什么在我的情况下这是一个问题是我从Json模式文件生成一组case-classes.Json自然支持联合类型,所以我想让我的案例类也反映出来.这在一个方向上很有用:用户创建要序列化到Json的case-classes.但是在另一个方向变得棘手:用户解析Json文件以返回一组填充的case类.

Owe*_*wen 8

解决这个问题的"标准"Scala解决方案是使用普通的区分联合类型(即,完全放弃真正的联合类型):

sealed trait Foo
case class IntFoo(x: Int) extends Foo
case class StringFoo(x: String) extends Foo
Run Code Online (Sandbox Code Playgroud)

这反映了这样一个事实,正如您所观察到的,成员的特定类型是运行时值; Foo实例的JVM类型标记提供此运行时值.

万里萨宾的实现工会类型是很聪明,但我不知道它是否提供任何实际的好处,因为它只能限制可进入事物的类型Foo,但提供的用户Foo与该限制的没有可计算版本,在方式a match为您提供可计算版本的sealed特征.一般来说,为了使限制变得有用,它需要两个方面:检查只有正确的东西被放入,以及提取器(也称为消除器)允许相同的正确的东西从另一端出来.

也许如果你给出一些解释为什么你正在寻找一个更纯粹的联合类型,它会说明常规的歧视联盟是否足够,或者你是否真的需要更多东西.