Map Scala中的不同类型

pic*_*ter 16 generics scala shapeless

我需要一个Map,我在其中放入不同类型的值(Double,String,Int,...),key可以是String.

有没有办法做到这一点,以便我得到正确的类型与map.apply(k)喜欢

val map: Map[String, SomeType] = Map()
val d: Double = map.apply("double")
val str: String = map.apply("string")
Run Code Online (Sandbox Code Playgroud)

我已经尝试了泛型类型

class Container[T](element: T) {
    def get: T = element
}

val d: Container[Double] = new Container(4.0)
val str: Container[String] = new Container("string")
val m: Map[String, Container] = Map("double" -> d, "string" -> str)
Run Code Online (Sandbox Code Playgroud)

但是由于Container需要参数,所以不可能.这有什么解决方案吗?

Rüd*_*ehn 22

这不是直截了当的.

值的类型取决于键.所以关键是必须携带有关其价值的类型的信息.这是一种常见的模式.它用于例如SBT(参见例如SettingsKey [T])和无形记录(示例).但是,在SBT中,键是一个巨大的,复杂的类层次结构,而无形的HList非常复杂,并且比您想要的更多.

所以这是一个如何实现这一点的小例子.密钥知道类型,创建记录或从记录中获取值的唯一方法是关键.我们在内部使用Map [Key,Any]作为存储,但是转换被隐藏并保证成功.有一个运算符可以从键创建记录,运算符可以合并记录.我选择了运算符,因此您可以连接记录而无需使用括号.

sealed trait Record {

  def apply[T](key:Key[T]) : T

  def get[T](key:Key[T]) : Option[T]

  def ++ (that:Record) : Record
}

private class RecordImpl(private val inner:Map[Key[_], Any]) extends Record {

  def apply[T](key:Key[T]) : T = inner.apply(key).asInstanceOf[T]

  def get[T](key:Key[T]) : Option[T] = inner.get(key).asInstanceOf[Option[T]]

  def ++ (that:Record) = that match {
    case that:RecordImpl => new RecordImpl(this.inner ++ that.inner)
  }
}

final class Key[T] {
  def ~>(value:T) : Record = new RecordImpl(Map(this -> value))
}

object Key {

  def apply[T] = new Key[T]
}
Run Code Online (Sandbox Code Playgroud)

以下是您将如何使用它.首先定义一些键:

val a = Key[Int]
val b = Key[String]
val c = Key[Float]
Run Code Online (Sandbox Code Playgroud)

然后使用它们来创建记录

val record = a ~> 1 ++ b ~> "abc" ++ c ~> 1.0f
Run Code Online (Sandbox Code Playgroud)

使用键访问记录时,您将获得正确类型的值

scala> record(a)
res0: Int = 1

scala> record(b)
res1: String = abc

scala> record(c)
res2: Float = 1.0
Run Code Online (Sandbox Code Playgroud)

我发现这种数据结构非常有用.有时您需要比案例类提供更多的灵活性,但您不希望使用像Map [String,Any]这样完全类型不安全的东西.这是一个很好的中间立场.


编辑:另一种选择是在内部使用(名称,类型)对作为真实键的映射.获取值时,您必须提供名称和类型.如果选择了错误的类型,则没有条目.然而,这有很大的错误可能性,比如当你输入一个字节并试图获得一个int.所以我认为这不是一个好主意.

import reflect.runtime.universe.TypeTag

class TypedMap[K](val inner:Map[(K, TypeTag[_]), Any]) extends AnyVal {
  def updated[V](key:K, value:V)(implicit tag:TypeTag[V]) = new TypedMap[K](inner + ((key, tag) -> value))

  def apply[V](key:K)(implicit tag:TypeTag[V]) = inner.apply((key, tag)).asInstanceOf[V]

  def get[V](key:K)(implicit tag:TypeTag[V]) = inner.get((key, tag)).asInstanceOf[Option[V]]
}

object TypedMap {
  def empty[K] = new TypedMap[K](Map.empty)
}
Run Code Online (Sandbox Code Playgroud)

用法:

scala> val x = TypedMap.empty[String].updated("a", 1).updated("b", "a string")
x: TypedMap[String] = TypedMap@30e1a76d

scala> x.apply[Int]("a")
res0: Int = 1

scala> x.apply[String]("b")
res1: String = a string

// this is what happens when you try to get something out with the wrong type.
scala> x.apply[Int]("b")
java.util.NoSuchElementException: key not found: (b,Int)
Run Code Online (Sandbox Code Playgroud)


Mil*_*bin 17

这在现在非常简单,没有形状,

scala> import shapeless._ ; import syntax.singleton._ ; import record._
import shapeless._
import syntax.singleton._
import record._

scala> val map = ("double" ->> 4.0) :: ("string" ->> "foo") :: HNil
map: ... <complex type elided> ... = 4.0 :: foo :: HNil

scala> map("double")
res0: Double with shapeless.record.KeyTag[String("double")] = 4.0

scala> map("string")
res1: String with shapeless.record.KeyTag[String("string")] = foo

scala> map("double")+1.0
res2: Double = 5.0

scala> val map2 = map.updateWith("double")(_+1.0)
map2: ... <complex type elided> ... = 5.0 :: foo :: HNil

scala> map2("double")
res3: Double = 5.0
Run Code Online (Sandbox Code Playgroud)

截至本答复日期,这是无形的2.0.0-SNAPSHOT.

  • 那非常整洁.但是当使用HList作为地图时,访问特征是什么?由于它基本上是一个单链表,你必须检查每个元素,所以查找将是O(N),对吧?此外,一旦你有多个元素,这不会创建非常复杂的类型吗? (3认同)