Dies ist nicht einfach.
Die Art des Wertes hängt vom Schlüssel ab. Also muss der Schlüssel die Information darüber tragen, um welchen Typ es sich handelt. Dies ist ein allgemeines Muster. Es wird zum Beispiel in SBT (siehe zum Beispiel SettingsKey[T]) und Shapeless Records (Example) verwendet. In SBT sind die Schlüssel jedoch eine riesige, komplexe Klassenhierarchie, und der HList in formlos ist ziemlich komplex und macht auch mehr, als Sie wollen.
Hier ist ein kleines Beispiel, wie Sie dies implementieren könnten.Der Schlüssel kennt den Typ, und die einzige Möglichkeit, einen Datensatz zu erstellen oder einen Wert aus einem Datensatz zu erhalten, ist der Schlüssel. Wir verwenden eine Map [Key, Any] intern als Speicher, aber die Casts sind versteckt und garantiert erfolgreich. Es gibt einen Operator zum Erstellen von Datensätzen über Schlüssel und einen Operator zum Zusammenführen von Datensätzen. Ich habe die Operatoren ausgewählt, damit Sie Datensätze verketten können, ohne Klammern zu verwenden.
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]
}
Hier ist, wie Sie dies verwenden würden. Zunächst einige Schlüssel definieren:
val a = Key[Int]
val b = Key[String]
val c = Key[Float]
Dann sie einen Datensatz
val record = a ~> 1 ++ b ~> "abc" ++ c ~> 1.0f
Beim Zugriff auf den Datensatz mit den Tasten erstellen, erhalten Sie einen Wert des richtigen Typs zurück
scala> record(a)
res0: Int = 1
scala> record(b)
res1: String = abc
scala> record(c)
res2: Float = 1.0
Ich finde diese Art von Datenstruktur sehr nützlich. Manchmal benötigen Sie mehr Flexibilität, als eine Fallklasse bietet, aber Sie möchten nicht auf etwas zurückgreifen, das völlig typsicher ist, wie eine Map [String, Any]. Dies ist ein guter Mittelweg.
Bearbeiten: Eine andere Option wäre eine Karte zu haben, die intern ein (Name, Typ) -Paar als reellen Schlüssel verwendet. Sie müssen sowohl den Namen als auch den Typ angeben, wenn Sie einen Wert erhalten. Wenn Sie den falschen Typ wählen, gibt es keinen Eintrag. Dies hat jedoch ein großes Potenzial für Fehler, wie wenn Sie ein Byte einfügen und versuchen, einen int herauszuholen. Also ich denke, das ist keine gute Idee.
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)
}
Verbrauch:
scala> val x = TypedMap.empty[String].updated("a", 1).updated("b", "a string")
x: TypedMap[String] = [email protected]
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)
Mögliches Duplikat von http://stackoverflow.com/questions/4309835/scala-reflection/4310959 –