首页 文章

联盟类型scala的集合

提问于
浏览
0

在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编译器将其视为[String,Any]的映射 . 有没有办法告诉scala编译器这是一个map [String,StringOrNumber]

编辑:

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

6 回答

  • 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))
    
  • 0

    Scala已经内置了 case -classes,它们能够表示其他类型的任意标记的不相交联合 .

    在您的情况下,定义 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]
    
  • 3

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

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

  • 0

    你应该写

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

    这个功能现在正在Dotty中开发 . 据我所知,它会是这样的

    Class[T1 | T2]
    

    Class[T1 & T2]
    

    但是dotty将在明年推出 . 现在,您可以使用您的方法,但这有点棘手,需要隐含 . 您也可以尝试使用任何一种类型(仅当您有2种泛型类型时),并且您还可以注意scalaz库 . 这都是关于类型级编程的 .

  • 2

    你试过了吗:

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

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

  • 1

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

    //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 .

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

    sizeOf[String](someExtendOfString)
    

相关问题