3

遵循关于如何在 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但是,如果直到运行时才知道参数怎么办?也许你正在解析一个包含 's 数据的文件,在这种情况下,直到你读取数据才知道Foo类型参数。在这种情况下,Foo没有简单的方法来设置参数。A

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

  • 对您已阅读的数据进行模式匹配,然后Foo根据该类型创建不同的 '。在我的情况下,这是不可行的,因为我的案例类实际上包含几十个联合类型,所以会有数百种类型的组合来进行模式匹配。

  • 将您刚刚阅读的类型转换为(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 模式文件生成一组案例类。Json 自然支持联合类型,所以我想让我的案例类也反映这一点。这在一个方向上很有效:用户创建要序列化到 Json 的案例类。但在另一个方向上变得棘手:用户解析 Json 文件以将一组填充的案例类返回给他们。

4

3 回答 3

8

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

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

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

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

也许如果您对为什么要寻找更纯粹的工会类型进行了一些解释,它将阐明常规的有区别的工会是否足够,或者您是否真的需要更多的东西。

于 2016-07-10T20:22:53.463 回答
2

Scala 的每个 JSON 解析器都需要定义明确的类型,以便将 JSON 转换为这些类型,即使必须删除某些字段也是有原因的:您不能使用不知道其类型的东西。

举个例子,假设你有a,也许a是 a String,也许是 a Int,但你不知道是什么。为什么你可以在a不知道它的类型的情况下进行计算?a例如,如果您事先不知道它是一个数字,为​​什么您的代码会计算所有 ' 的总和?

通常,对此的答案是在运行时对具有未知特征的数据执行用户提供的数据操作,因为用户自己看到它是一个数字并决定他们想知道该字段的总和是多少。很好,但如果是这样,你就走错了路。

在 Scala 中有一种定义明确的方式来表示 JSON 数据(以及,就此而言,任何与 JSON 具有相同特征的数据。它使用类的层次结构。json 值可以是 json 对象、数组或其中之一许多原语。一个json对象包含一个键/值对列表,其键是json字符串,值是json值等等。这很容易表示,并且已经有很多库这样做了。事实上,有这么多,有一个名为Json4s的项目,它提供了一个统一的 API,可以由上述许多库使用和实现。

诸如 Miles Sabin 的Shapeless库提供的记录之类的东西是在输入没有明确定义的模式时使用的,但程序知道它需要从该输入中得到什么。a而且,是的,如果它是 anInt或 a ,程序可能知道如何处理String,但不是所有可能的值。

于 2016-07-10T21:09:04.197 回答
2

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

您可以在“ a tour of 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() 
} 
于 2019-06-12T19:28:39.130 回答