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

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (2)
  • 关注 (0)
  • 查看 (7)

接下来就如何在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!

此示例有效,因为参数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
}

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

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

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

提问于
用户回答回答于

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

sealed trait Foo
case class IntFoo(x: Int) extends Foo
case class StringFoo(x: String) extends Foo

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

Miles Sabin的联合类型实现非常聪明,但我不确定它是否提供任何实际好处,因为它只限制了可以进入a的东西的类型Foo,但是为用户提供了Foo没有可计算版本的限制,在方式a match为您提供可计算版本的sealed特征。一般来说,为了使限制变得有用,它需要两个方面:检查只有正确的东西被放入,以及提取器(也称为消除器)允许相同的正确的东西从另一端出来。

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

用户回答回答于

基于Dotty的下一个Scala 3(2020年中)将从2018年9月开始实施Union Type提案

你在“ Scala 3之旅 ”中看到它(2019年6月)

  • 联合类型提供类型的临时组合
  • 子集=子类型
  • 没有拳击开销

case class UserName(name: String) 
case class Password(hash: Hash) 

def help(id: UserName | Password) = {
  val user = id match { 
    case UserName(name) => lookupName(name) 
    case Password(hash) => lookupPassword(hash) 
  } 
  ... 
}

  • 联合类型也适用于单例类型
  • 非常适合JS互操作

type Command = "Click" | "Drag" | "KeyPressed" 

def handleEvent(kind: Command) = kind match {
  case "Click" => MouseClick() 
  case "Drag" => MoveTo() 
  case "KeyPressed" => KeyPressed() 
} 

扫码关注云+社区

领取腾讯云代金券