联合类型Scala的集合

2wnc66cl  于 2022-11-09  发布在  Scala
关注(0)|答案(7)|浏览(212)

在Scala中可以有一个联合类型的集合吗?here讨论了几种联合类型的方法评分最高的答案感觉最本地化,我有这样的答案:

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

但当我试图制作一张既包含这两项内容的Map时

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

Scala编译器将其视为[字符串,任意]的Map。有什么方法可以告诉Scala编译器这是一个Map[字符串,StringOrNumber]
编辑:
我不认为使用上面的方法可以创建一个字符串或联合的集合。我认为它需要是联合类型的另一种方法,因为上面类似于重载方法,而不是类型系统中真正的联合类型

raogr8fs

raogr8fs1#

您可以在当前版本的Scala中对运行时联合类型进行最接近的模拟,就是将联合的类型 Package 在Case类中,扩展一些密封的特征。它是样板-y,并在AnyRef类型上添加了一个额外的 Package 层,但它是有效的,它比只使用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))
imzjd6km

imzjd6km2#

Scala已经有内置的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]
nkkqxpd9

nkkqxpd93#

您复制的部分答案不完整。有火柴的另一个部分。它表明,这种类型的联合在运行时工作。因此,总的来说,您混合了两种不同的东西:编译时类型Union(也讨论了您提到的问题,最初是由Miles Sabin here编写的),它影响编译器检查,以及运行时类型检查。
所以,一旦您使用运行时方法,Scala编译器就不理解这个联合,并建议使用任何

krugob8w

krugob8w4#

你应该写信给我

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

这一功能目前正在Dotty开发中。据我所知,这就像是

Class[T1 | T2]

Class[T1 & T2]

但Dotty将在明年上市。现在,您可以使用您的方法,但它有一点棘手,并且需要暗示。您也可以尝试任何一种类型(只有在您有两个泛型类型的情况下),并且您还可以关注scalaz库。这一切都是关于类型级编程的。

9jyewag0

9jyewag05#

你试过了吗:

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

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

9rygscc1

9rygscc16#

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

//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或字符串的扩展。在此处输入的值被检查为与Int with String“相反”。
有一种方法可以解决这个问题,您必须绕过类型推断,并在类型参数中提供基类,如下所示:

sizeOf[String](someExtendOfString)
chy5wohz

chy5wohz7#

自那以后,Scala3发布了对联合类型的本机支持。

type StringOrNumber = String | Int

相关问题