DavidGamba
DavidGamba

Reputation: 3613

best way to implicitly convert Map of Any to Map of Class when extracting the value

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

Answers (3)

dk14
dk14

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

Ryoichiro Oka
Ryoichiro Oka

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

DavidGamba
DavidGamba

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

Related Questions