这并不简单。
值的类型取决于键。所以键必须携带关于它的值是什么类型的信息。这是一种常见的模式。例如,它用于 SBT(参见SettingsKey[T])和无形记录(Example)。然而,在 SBT 中,键是一个巨大的、复杂的类层次结构,而 shapeless 中的 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]
}
这是您将如何使用它的方法。首先定义一些键:
val a = Key[Int]
val b = Key[String]
val c = Key[Float]
然后使用它们创建记录
val record = a ~> 1 ++ b ~> "abc" ++ c ~> 1.0f
使用键访问记录时,您将返回正确类型的值
scala> record(a)
res0: Int = 1
scala> record(b)
res1: String = abc
scala> record(c)
res2: Float = 1.0
我发现这种数据结构非常有用。有时您需要比案例类提供的更多灵活性,但您不想求助于完全类型不安全的东西,例如 Map[String,Any]。这是一个很好的中间立场。
编辑:另一种选择是有一个使用 (name, type) 对作为内部真正键的映射。获取值时必须提供名称和类型。如果您选择了错误的类型,则没有条目。然而,这有很大的潜在错误,比如当你输入一个字节并试图取出一个 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)
}
用法:
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)