What is the best way to create and pass around dic

2020-06-28 16:17发布

问题:

By dictionary I mean a lightweight map from names to values that can be used as the return value of a method.

Options that I'm aware of include making case classes, creating anon objects, and making maps from Strings -> Any.

  • Case classes require mental overhead to create (names), but are strongly typed.
  • Anon objects don't seem that well documented and it's unclear to me how to use them as arguments since there is no named type.
  • Maps from String -> Any require casting for retrieval.

Is there anything better?

Ideally these could be built from json and transformed back into it when appropriate.

I don't need static typing (though it would be nice, I can see how it would be impossible) - but I do want to avoid explicit casting.

回答1:

Here's the fundamental problem with what you want:

def get(key: String): Option[T] = ...

val r = map.get("key")

The type of r will be defined from the return type of get -- so, what should that type be? From where could it be defined? If you make it a type parameter, then it's relatively easy:

import scala.collection.mutable.{Map => MMap}
val map: MMap[String, (Manifest[_], Any) = MMap.empty
def get[T : Manifest](key: String): Option[T] = map.get(key).filter(_._1 <:< manifest[T]).map(_._2.asInstanceOf[T])
def put[T : Manifest](key: String, obj: T) = map(key) = manifest[T] -> obj

Example:

scala> put("abc", 2)

scala> put("def", true)

scala> get[Boolean]("abc")
res2: Option[Boolean] = None

scala> get[Int]("abc")
res3: Option[Int] = Some(2)

The problem, of course, is that you have to tell the compiler what type you expect to be stored on the map under that key. Unfortunately, there is simply no way around that: the compiler cannot know what type will be stored under that key at compile time.

Any solution you take you'll end up with this same problem: somehow or other, you'll have to tell the compiler what type should be returned.

Now, this shouldn't be a burden in a Scala program. Take that r above... you'll then use that r for something, right? That something you are using it for will have methods appropriate to some type, and since you know what the methods are, then you must also know what the type of r must be.

If this isn't the case, then there's something fundamentally wrong with the code -- or, perhaps, you haven't progressed from wanting the map to knowing what you'll do with it.



回答2:

So you want to parse json and turn it into objects that resemble the javascript objets described in the json input? If you want static typing, case classes are pretty much your only option and there are already libraries handling this, for example lift-json.

Another option is to use Scala 2.9's experimental support for dynamic typing. That will give you elegant syntax at the expense of type safety.



回答3:

You can use approach I've seen in the casbah library, when you explicitly pass a type parameter into the get method and cast the actual value inside the get method. Here is a quick example:

case class MultiTypeDictionary(m: Map[String, Any]) {
  def getAs[T <: Any](k: String)(implicit mf: Manifest[T]): T =
    cast(m.get(k).getOrElse {throw new IllegalArgumentException})(mf)
private def cast[T <: Any : Manifest](a: Any): T = a.asInstanceOf[T] }
implicit def map2multiTypeDictionary(m: Map[String, Any]) = MultiTypeDictionary(m)
val dict: MultiTypeDictionary = Map("1" -> 1, "2" -> 2.0, "3" -> "3")
val a: Int = dict.getAs("1") val b: Int = dict.getAs("2") //ClassCastException val b: Int = dict.getAs("4") //IllegalArgumetExcepton

You should note that there is no real compile-time checks, so you have to deal with all exceptions drawbacks.

UPD Working MultiTypeDictionary class



回答4:

If you have only a limited number of types which can occur as values, you can use some kind of union type (a.k.a. disjoint type), having e.g. a Map[Foo, Bar | Baz | Buz | Blargh]. If you have only two possibilities, you can use Either[A,B], giving you a Map[Foo, Either[Bar, Baz]]. For three types you might cheat and use Map[Foo, Either[Bar, Either[Baz,Buz]]], but this syntax obviously doesn't scale well. If you have more types you can use things like...

  • http://cleverlytitled.blogspot.com/2009/03/disjoint-bounded-views-redux.html
  • http://svn.assembla.com/svn/metascala/src/metascala/OneOfs.scala
  • http://www.chuusai.com/2011/06/09/scala-union-types-curry-howard/


标签: scala