联盟类型scala的集合

时间:2018-02-19 15:17:18

标签: scala functional-programming scala-collections discriminated-union

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编译器这是一个map [String,StringOrNumber]

编辑:

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

6 个答案:

答案 0 :(得分:3)

最接近的运行时联合类型仿真,您可以在当前版本的Scala中执行,是在扩展一些密封特征的case类中包装union的类型。它是样板文件,并在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))

答案 1 :(得分:3)

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]

答案 2 :(得分:2)

您复制的部分答案尚未完成。还有另一部分匹配。并且它表明这种类型的联合在运行时工作。 所以一般来说,你混合了两个不同的东西:编译时类型联合(这也是你提到的问题,最初是由Miles Sabin here编写的),它影响编译器检查和运行时类型检查。

所以,只要你使用运行时方法,scala编译器就是不理解这个联合,并建议使用Any

答案 3 :(得分:1)

你应该写

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

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

Class[T1 | T2] 

Class[T1 & T2] 

但是dotty将在明年推出。 现在,你可以使用你的方法,但它有点棘手,需要暗示。 您也可以尝试使用任何一种类型(仅当您拥有2种通用类型时),并且您还可以关注scalaz库。这完全是关于类型级编程的。

答案 4 :(得分:0)

你有没有尝试过:

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

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

答案 5 :(得分:0)

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

//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的扩展。这里输入的值被检查约束为&#34;逆变&#34;到Int with String

有一种解决方法,你必须绕过类型推断,并在type参数中提供基类,如下所示:

sizeOf[String](someExtendOfString)