联合类型集合 scala

Collection of Union types scala

在 scala 中是否可以有联合类型的集合。讨论了几种联合类型的方法 here 评分最高的答案感觉最原生,我有这样的东西:

sealed trait StringOrNumber[T]
object StringOrNumber {
    implicit object IntWitness extends StringOrNumber[Int]
    implicit object StringWitness extends StringOrNumber[String]
}

但是当我尝试制作包含两者的地图时

val m: Map[String, Any] = Map("str" -> "hellp", "int" -> 32)

scala 编译器将其视为 [String,Any] 的映射 有没有办法告诉 scala 编译器这是一个映射 [String, StringOrNumber]

编辑:

我不认为使用上述方法可以创建字符串或联合的集合。我认为它需要成为联合类型的另一种方法,因为上面的方法类似于重载方法,而不是类型系统中的真正联合类型

你试过了吗:

val m: Map[String, StringOrNumber] = Map("str" -> "hellp", "int" -> 32)

在这种情况下,您可能还需要显式构造 StringOrNumber 实例才能使其正常工作。

你应该写

val m: Map[String, StringOrNumber[_]] = ...

此功能目前正在 Dotty 中开发。据我所知,它会像

Class[T1 | T2] 

Class[T1 & T2] 

但 dotty 将在明年上市。 现在,您可以使用您的方法,但它有点棘手,需要隐含。 您也可以尝试 Either 类型(只有当您有 2 个泛型类型时),您还可以关注 scalaz 库。这都是关于 type-level 编程的。

您复制的部分答案不完整。还有另一部分与匹配。它表明这种类型的联合在运行时有效。 所以一般来说,你会混合两种不同的东西:编译时类型联合(你提到的问题中也讨论过,最初由 Miles Sabin here 编写)影响编译器检查,以及运行时类型检查。

所以,一旦你使用运行时方法,scala 编译器就不会理解这个联合,建议使用 Any

您可以在当前版本的 Scala 中执行的最接近运行时联合类型的模拟是包装联合类型,以防 类 扩展某些密封特征。它是 boilerplate-y 并在 AnyRef 类型上添加了一个额外的包装层,但它有效,它比仅使用 Any 更好,您还可以从联合类型添加隐式转换:

sealed trait StringOrNumber
object StringOrNumber {
  case class IsNumber(i: Int) extends StringOrNumber
  case class IsString(s: String) extends StringOrNumber

  implicit def isNumber(i: Int): StringOrNumber = IsNumber(i)
  implicit def isString(s: String): StringOrNumber = IsString(s)
}

现在您可以定义您的 Map:

scala> val m: Map[String, StringOrNumber] = Map("str" -> "hellp", "int" -> 32)
m: Map[String,StringOrNumber] = Map(str -> IsString(hellp), int -> IsNumber(32))

Scala 已经有 built-in case-类,它们能够表示其他类型的任意标记的不相交联合。

在您的情况下,定义 StringOrNumber 的最简单方法是:

sealed trait StringOrNumber
case class Num(n: Int) extends StringOrNumber
case class Str(s: String) extends StringOrNumber

val m: Map[String, StringOrNumber] = Map(
  "str" -> Str("hellp"), 
  "int" -> Num(42)
)

for ((k, v) <- m) {
  v match {
    case Num(n) => println("It's an int: " + n)
    case Str(s) => println("A string: " + s)
  }
}

如果你不想为此创建额外的特征,并且如果你只有两种类型,只需使用 Either:

type StringOrNum = Either[String, Int]

让我向您介绍我的解决方案,使用逆变和类型约束:

//Add this to your util library
trait Contra[-A]
type Union[A,B] = Contra[A] <:< Contra[B]

//And see a usage example below
@implicitNotFound("Only Int or String can be sized")
type Sizeable[T] = Union[T, Int with String]

def sizeOf[T: Sizeable](sizeable: T): Int = {
  sizeable match {
    case i: Int => i
    case s: String => s.length
  }
}

此解决方案的问题是此处不接受 Int 或 String 的扩展。此处输入的值被检查为 "Contravariant" 到 Int with String.

有办法解决这个问题,你必须绕过类型推断,并在类型参数中提供基数 class,如下所示:

sizeOf[String](someExtendOfString)