Reputation: 3613
When I create a mixed Map, with booleans and strings:
scala> val map = Map('boolean -> true, 'string -> "string")
map: scala.collection.immutable.Map[Symbol,Any] = Map('boolean -> true, 'string -> string)
And I try to directly access the boolean part, I get:
scala> if (map('boolean)) true else false
<console>:9: error: type mismatch;
found : Any
required: Boolean
if (map('boolean)) true else false
^
So I have to define an implicit to make that work:
scala> implicit def anyAsBoolean(x: Any) = x.asInstanceOf[Boolean]
warning: there were 1 feature warning(s); re-run with -feature for details
anyAsBoolean: (x: Any)Boolean
scala> if (map('boolean)) true else false
res3: Boolean = true
Is there a way to make this Map do the implicit conversion without having to add the implicit part in the client code?
Upvotes: 0
Views: 1308
Reputation: 22374
You may try Shapeless HMap
:
class BiMapIS[K, V]
implicit val intToString = new BiMapIS[Int, String]
implicit val stringToInt = new BiMapIS[String, Int]
val hm = HMap[BiMapIS](23 -> "foo", "bar" -> 13)
//val hm2 = HMap[BiMapIS](23 -> "foo", 23 -> 13) // Does not compile - strong type
scala> hm.get(23)
res0: Option[String] = Some(foo)
scala> hm.get("bar")
res1: Option[Int] = Some(13)
So, client don't need to cast your Map explicitly or write any implicit convertions (intToString, stringToInt needed only for map definition). Note that type of HMap will be bounded with {Int -> String, String -> Int}
and you also can't do {String -> A; String -> B}
, so strings should be replaced with some case objects if it's possible.
Upvotes: 4
Reputation: 1997
Why don't you cast it right there:
map('boolean).asInstanceOf[Boolean]
Or you may use Either
to keep the type info:
val map: Map[Symbol, Either[Any, Boolean]] = Map(
'boolean -> Right(true),
'string -> Left("string"))
map('boolean).fold(throw new IllegalStateException(_), b => b)
Using Scalaz:
val map: Map[Symbol, Either[Any, Boolean]] = Map(
'boolean -> true.right[Any],
'string -> "string".left[Boolean])
map 'boolean | throw new IllegalStateException
Upvotes: 3
Reputation: 3613
So I ended up Inheriting from Map[Symbol, Any]
and doing the extractions in this way:
scala> :paste
// Entering paste mode (ctrl-D to finish)
object OptionMap {
def apply(options_map: Map[Symbol, Any]) = new OptionMap(options_map)
def apply(kv: (Symbol, Any)*) = new OptionMap(kv.toMap)
}
class OptionMap(options_map: Map[Symbol, Any]) extends Map[Symbol, Any] {
def apply[T](name: Symbol) : T = options_map(name).asInstanceOf[T]
def get[T](name: Symbol) : T = options_map(name).asInstanceOf[T]
def get(name: Symbol) : Option[Any] = options_map.get(name)
override def contains(name: Symbol): Boolean = options_map.contains(name)
def +[B1 >: Any](kv: (Symbol, B1)): OptionMap = OptionMap(options_map + kv)
def -(key: Symbol): OptionMap = OptionMap(options_map - key)
def iterator = options_map.iterator
}
// Exiting paste mode, now interpreting.
defined module OptionMap
defined class OptionMap
scala> val omap = OptionMap('boolean -> true, 'string -> "string", 'int -> 5, 'double -> 3.14)
omap: OptionMap = Map('boolean -> true, 'string -> string, 'int -> 5, 'double -> 3.14)
scala> if(omap.contains('boolean) && omap('boolean)) true else false
res0: Boolean = true
scala> omap[String]('string) + " world!"
res1: String = string world!
scala> omap[Int]('int) + 3
res2: Int = 8
scala> omap[Double]('double) + 3
res3: Double = 6.140000000000001
scala> omap + ('extra -> true)
res4: OptionMap = Map('string -> string, 'double -> 3.14, 'boolean -> true, 'int -> 5, 'extra -> true)
scala> omap - 'extra
res5: OptionMap = Map('boolean -> true, 'string -> string, 'int -> 5, 'double -> 3.14)
So it now does what I wanted, the client code doesn't need to have any implicits. Of course, the client needs to know the type what is stored in each key -> value
pair but that is meant to be.
Upvotes: 0