I am trying and failing to get something like this to work in Scala 3:
type TupleK[K[*], V[*], A] = (K[A], V[A])
final class MapK[K[*], V[*]] private (val rawMap: Map[K[?], V[?]]) {
def foreach(f: TupleK[K, V, ?] => Unit): Unit = {
rawMap.foreach(f.asInstanceOf[Tuple2[K[?], V[?]] => Any])
}
}
object MapK {
def apply[K[*], V[*]](entries: TupleK[K, V, ?]*): MapK[K, V] = {
new MapK[K, V](Map(entries: _*))
}
}
With usage like this:
class Key[A]()
type Id[A] = A
val intKey = Key[Int]
val strKey = Key[String]
MapK[Key, Id](intKey -> 1, strKey -> "a")
In Scala 2 that works, just need to adjust syntax by replacing * and ? with _ (except in _* of course).
In Scala 3 however basically every line errors with "unreducible application of higher-kinded type to wildcard arguments": Scastie.
The docs say that existential types have been dropped in Scala 3, however they don't really give any non-trivial examples of how to deal with this.
The docs mention that "Existential types largely overlap with path-dependent types" – can this MapK be implemented with path-dependent types? I've read this but didn't understand how to apply that, or whether it's possible in my case.
And, if not path dependent types... then what? It seems unlikely that Scala was "simplified" to the point where it's impossible to implement this functionality anymore, so I must be missing something.
ETA: In addition to my own answers below, I made this repo and wrote this article about the various approaches to encoding MapK in Scala 3.
I was able to produce a working, albeit incredibly annoying, implementation. This pointer was especially valuable.
First, a few notes:
Type inference on this sucks on many levels. All the manual type ascribtions in the tests, and all of the implicit conversions below are needed for this to work.
Apparently Scala is not smart enough to figure out that A and type Id[A] = A are functionally the same when looking for implicits, so a combinatorial explosion of ad-hoc implicit conversions are needed. Ugly and not very scalable.
Observe the different options available in Scala 3: foreach, foreachT, and foreachK. All of them have stylistic tradeoffs.
If you can improve on any of this, please let me know. This works, but it was so much nicer in Scala 2.
MapK implementation:
class MapK[K[_], V[_]] protected(protected val rawMap: Map[Type[K], Type[V]]) {
def apply[A](key: K[A]): V[A] = {
rawMap(key).asInstanceOf[V[A]]
}
def updated[A](key: K[A], value: V[A]): MapK[K, V] = {
MapK.unsafeCoerce(rawMap.updated(key, value))
}
def updated[A](pair: (K[A], V[A])): MapK[K, V] = {
MapK.unsafeCoerce(rawMap.updated(pair._1, pair._2))
}
def foreach[A](f: ((K[A], V[A])) => Unit): Unit = {
rawMap.foreach(f.asInstanceOf[(([Type[K], Type[V]])) => Any])
}
def foreachT(f: Type.Tuple2[K, V] => Unit): Unit = {
foreach { (k, v) => f((k, v)) }
}
def foreachK(f: [A] => (K[A], V[A]) => Unit): Unit = {
foreach { (k, v) => f(k, v) }
}
}
object MapK {
def unsafeCoerce[K[_], V[_]](rawMap: Map[Type[K], Type[V]]): MapK[K, V] = {
new MapK[K, V](rawMap)
}
def apply[K[_], V[_]](entries: Type.Tuple2[K, V]*): MapK[K, V] = {
new MapK[K, V](Map(entries.asInstanceOf[Seq[(Type[K], Type[V])]]: _*))
}
}
Other methods in MapK that you might want to implement basically follow the same patterns as foreach, foreachT, or foreachK.
And now, usage:
def test(caption: String)(code: => Unit): Unit = code
def assertEquals[A](a: A, b: A): Unit = assert(a == b)
case class Key[A](label: String, default: A)
val boolKey = Key[Boolean]("bool", false)
val intKey = Key[Int]("int", 0)
val strKey = Key[String]("str", "")
val optionMap = MapK[Key, Option](boolKey -> Some(true), intKey -> Some(1), strKey -> Option("a"), strKey -> None)
val idMap = MapK[Key, Id](boolKey -> true, intKey -> 1, strKey -> "hello")
val expectedOptionValues = List[Type.Tuple3[Key, Option, Id]](
(boolKey, Some(true), false),
(intKey, Some(1), 0),
(strKey, None, "")
)
val expectedIdValues = List[Type.Tuple3[Key, Id, Id]](
(boolKey, true, false),
(intKey, 1, 0),
(strKey, "hello", "")
)
test("optionMap - apply & updated") {
assertEquals(optionMap(intKey), Some(1))
assertEquals(optionMap(strKey), None)
assertEquals(optionMap.updated(strKey, Some("yo"))(strKey), Some("yo"))
}
test("optionMap - foreach") {
var values: List[Type.Tuple3[Key, Option, Id]] = Nil
optionMap.foreach { (k, v) =>
values = values :+ (k, v, k.default)
}
assertEquals(values, expectedOptionValues)
}
test("optionMap - foreachT") {
var values: List[Type.Tuple3[Key, Option, Id]] = Nil
optionMap.foreachT { pair => // no parameter untupling :(
values = values :+ (pair._1, pair._2, pair._1.default)
}
assertEquals(values, expectedOptionValues)
}
test("optionMap - foreachK") {
var values: List[Type.Tuple3[Key, Option, Id]] = Nil
optionMap.foreachK {
[A] => (k: Key[A], v: Option[A]) => // need explicit types :(
values = values :+ (k, v, k.default)
}
assertEquals(values, expectedOptionValues)
}
test("idMap - apply & updated") {
assertEquals(idMap(intKey), 1)
assertEquals(idMap(strKey), "hello")
assertEquals(idMap.updated(strKey, "yo")(strKey), "yo")
}
test("idMap - foreach") {
var values: List[Type.Tuple3[Key, Id, Id]] = Nil
idMap.foreach { (k, v) =>
values = values :+ (k, v, k.default)
}
assertEquals(values, expectedIdValues)
}
test("idMap - foreachT") {
var values: List[Type.Tuple3[Key, Id, Id]] = Nil
idMap.foreachT { pair =>
values = values :+ (pair._1, pair._2, pair._1.default)
}
assertEquals(values, expectedIdValues)
}
test("idMap - foreachK") {
var values: List[Type.Tuple3[Key, Id, Id]] = Nil
idMap.foreachK {
[A] => (k: Key[A], v: A) =>
values = values :+ (k, v, k.default)
}
assertEquals(values, expectedIdValues)
}
And now, the support cast that makes this work:
import scala.language.implicitConversions // old style, but whatever
type Id[A] = A
type Type[F[_]] <: (Any { type T })
object Type {
type Tuple2[F[_], G[_]] <: (Any { type T })
type Tuple3[F[_], G[_], H[_]] <: (Any { type T })
}
implicit def wrap[F[_], A](value: F[A]): Type[F] =
value.asInstanceOf[Type[F]]
implicit def wrapT2[F[_], G[_], A](value: (F[A], G[A])): Type.Tuple2[F, G] =
value.asInstanceOf[Type.Tuple2[F, G]]
implicit def wrapT2_P1[F[_], A](t: (F[A], A)): Type.Tuple2[F, Id] = wrapT2[F, Id, A](t)
implicit def wrapT3[F[_], G[_], H[_], A](value: (F[A], G[A], H[A])): Type.Tuple3[F, G, H] =
value.asInstanceOf[Type.Tuple3[F, G, H]]
implicit def wrapT3_P1[F[_], G[_], A](value: (F[A], A, A)): Type.Tuple3[F, Id, Id] =
value.asInstanceOf[Type.Tuple3[F, Id, Id]]
implicit def wrapT3_P1_P2[F[_], G[_], A](value: (F[A], G[A], A)): Type.Tuple3[F, G, Id] =
value.asInstanceOf[Type.Tuple3[F, G, Id]]
implicit def unwrap[F[_]](value: Type[F]): F[value.T] =
value.asInstanceOf[F[value.T]]
implicit def unwrapT2[F[_], G[_]](value: Type.Tuple2[F, G]): (F[value.T], G[value.T]) =
value.asInstanceOf[(F[value.T], G[value.T])]
implicit def unwrapT3[F[_], G[_], H[_]](value: Type.Tuple3[F, G, H]): (F[value.T], G[value.T], H[value.T]) =
value.asInstanceOf[(F[value.T], G[value.T], H[value.T])]
Here's an alternative solution, using dependent types. In general I like it better, it's more obvious to me what's going on.
import scala.language.implicitConversions
type Id[A] = A
implicit def wrapId[A](a: A): Id[A] = a
implicit def unwrapId[A](a: Id[A]): A = a
case class Key[A](caption: String, default: A)
val boolKey = Key[Boolean]("bool", false)
val intKey = Key[Int]("int", 0)
val strKey = Key[String]("str", "")
type KTuple[K[_], V[_]] = {
type T;
type Pair = (K[T], V[T]);
}
implicit def KTuple[K[_], V[_], A](value: (K[A], V[A])): KTuple[K, V]#Pair = value.asInstanceOf[KTuple[K, V]#Pair]
implicit def KTuple_P1[K[_], A](value: (K[A], A)): KTuple[K, Id]#Pair = value.asInstanceOf[KTuple[K, Id]#Pair]
class MapK[K[_], V[_]](rawMap: Map[K[Any], V[Any]]) {
def foreachK(f: [A] => (K[A], V[A]) => Unit): Unit = {
rawMap.foreach(f.asInstanceOf[((K[Any], V[Any])) => Unit])
}
def foreach(f: KTuple[K, V]#Pair => Unit): Unit = {
rawMap.foreach { pair =>
f(pair.asInstanceOf[KTuple[K, V]#Pair])
}
}
}
object MapK {
def create[K[_], V[_]](pairs: KTuple[K, V]#Pair*): MapK[K, V] = {
val x: List[KTuple[K, V]#Pair] = pairs.toList
val y: List[(K[Any], V[Any])] = x.map(t => t.asInstanceOf[(K[Any], V[Any])])
new MapK(Map(y: _*))
}
}
val idMap = MapK.create[Key, Id](
boolKey -> false,
intKey -> 1,
strKey -> "a",
)
val optionMap = MapK.create[Key, Option](
intKey -> Some(1),
strKey -> Some("a")
)
type T3[A] = (Key[A], A, A)
var log = List[KTuple[Key, Option]#Pair]()
idMap.foreach { (k, v) =>
log = log.appended(KTuple(k, Some(v)))
}
def doSomething[A, V[_]](k: Key[A], v: V[A]): Unit = println(s"$k -> v")
optionMap.foreachK {
[A] => (k: Key[A], v: Option[A]) => {
doSomething(k, v.get)
doSomething(k, v)
log = log :+ KTuple((k, v))
}
}
I wrote up a blog post with more details, will publish soon after some editing. Still looking for better approaches and improvements though.
What is the simplest way to convert a java.util.IdentityHashMap[A,B] into a subtype of scala.immutable.Map[A,B]? I need to keep keys separate unless they are eq.
Here's what I've tried so far:
scala> case class Example()
scala> val m = new java.util.IdentityHashMap[Example, String]()
scala> m.put(Example(), "first!")
scala> m.put(Example(), "second!")
scala> m.asScala // got a mutable Scala equivalent OK
res14: scala.collection.mutable.Map[Example,String] = Map(Example() -> first!, Example() -> second!)
scala> m.asScala.toMap // doesn't work, since toMap() removes duplicate keys (testing with ==)
res15: scala.collection.immutable.Map[Example,String] = Map(Example() -> second!)
Here's a simple implementation of identity map in Scala. In usage, it should be similar to standard immutable map.
Example usage:
val im = IdentityMap(
new String("stuff") -> 5,
new String("stuff") -> 10)
println(im) // IdentityMap(stuff -> 5, stuff -> 10)
Your case:
import scala.collection.JavaConverters._
import java.{util => ju}
val javaIdentityMap: ju.IdentityHashMap = ???
val scalaIdentityMap = IdentityMap.empty[String,Int] ++ javaIdentityMap.asScala
Implementation itself (for performance reasons, there may be some more methods that need to be overridden):
import scala.collection.generic.ImmutableMapFactory
import scala.collection.immutable.MapLike
import IdentityMap.{Wrapper, wrap}
class IdentityMap[A, +B] private(underlying: Map[Wrapper[A], B])
extends Map[A, B] with MapLike[A, B, IdentityMap[A, B]] {
def +[B1 >: B](kv: (A, B1)) =
new IdentityMap(underlying + ((wrap(kv._1), kv._2)))
def -(key: A) =
new IdentityMap(underlying - wrap(key))
def iterator =
underlying.iterator.map {
case (kw, v) => (kw.value, v)
}
def get(key: A) =
underlying.get(wrap(key))
override def size: Int =
underlying.size
override def empty =
new IdentityMap(underlying.empty)
override def stringPrefix =
"IdentityMap"
}
object IdentityMap extends ImmutableMapFactory[IdentityMap] {
def empty[A, B] =
new IdentityMap(Map.empty)
private class Wrapper[A](val value: A) {
override def toString: String =
value.toString
override def equals(other: Any) = other match {
case otherWrapper: Wrapper[_] =>
value.asInstanceOf[AnyRef] eq otherWrapper.value.asInstanceOf[AnyRef]
case _ => false
}
override def hashCode =
System.identityHashCode(value)
}
private def wrap[A](key: A) =
new Wrapper(key)
}
One way to handle this would be change what equality means for the class, e.g.
scala> case class Example() {
override def equals( that:Any ) = that match {
case that:AnyRef => this eq that
case _ => false
}
}
defined class Example
scala> val m = new java.util.IdentityHashMap[Example, String]()
m: java.util.IdentityHashMap[Example,String] = {}
scala> m.put(Example(), "first!")
res1: String = null
scala> m.put(Example(), "second!")
res2: String = null
scala> import scala.collection.JavaConverters._
import scala.collection.JavaConverters._
scala> m.asScala
res3: scala.collection.mutable.Map[Example,String] = Map(Example() -> second!, Example() -> first!)
scala> m.asScala.toMap
res4: scala.collection.immutable.Map[Example,String] = Map(Example() -> second!, Example() -> first!)
Or if you don't want to change equality for the class, you could make a wrapper.
Of course, this won't perform as well as a Map that uses eq instead of ==; it might be worth asking for one....
I have a lot of client code that build Map using the same keys (to query MongoDB).
My idea is to provide helper methods that hide keys.
First try, I have used default parameters (cf object Builder below) but the client hava to deal with Option
I now use a builder pattern (cf class Builder below)
Is there a better way ?
class Builder {
val m = collection.mutable.Map[String, Int]()
def withA(a: Int) = {m += (("a", a))}
def withB(b: Int) = {m += (("b", b))}
def withC(c: Int) = {m += (("c", c))}
def build = m.toMap
}
object Builder {
def build1(a: Option[Int] = None, b: Option[Int] = None, c: Option[Int] = None): Map[String, Int] = {
val optPairs = List(a.map("a" -> _),
b.map("b" -> _),
c.map("c" -> _))
val pairs = optPairs.flatten
Map(pairs: _*)
}
}
object Client {
def main(args: Array[String]) {
println(Builder.build1(b = Some(2)))
println(new Builder().withB(2))
}
}
An easy solution to avoid having to deal with options when calling Builder.build1 is to define an implicit conversion to automatically wrap any value into an Some:
implicit def wrap[T]( x: T ) = Some( x )
And boom, you can omit the wrapping and directly do:
scala> Builder.build1( a = 123, c = 456 )
res1: Map[String,Int] = Map(a -> 123, c -> 456)
However, this is pretty dangerous given that options are pervasive and you don't want to pull such a general converion into scope.
To fix this you can define your own "option" class that you'll use just for the purpose of defining those optional parameters:
abstract sealed class OptionalArg[+T] {
def toOption: Option[T]
}
object OptionalArg{
implicit def autoWrap[T]( value: T ): OptionalArg[T] = SomeArg(value)
implicit def toOption[T]( arg: OptionalArg[T] ): Option[T] = arg.toOption
}
case class SomeArg[+T]( value: T ) extends OptionalArg[T] {
def toOption = Some( value )
}
case object NoArg extends OptionalArg[Nothing] {
val toOption = None
}
You can then redefine Build.build1 as:
def build1(a: OptionalArg[Int] = NoArg, b: OptionalArg[Int] = NoArg, c: OptionalArg[Int] = NoArg): Map[String, Int]
And then once again, you can directly call Build.build1 without explicitely wrapping the argument with Some:
scala> Builder.build1( a = 123, c = 456 )
res1: Map[String,Int] = Map(a -> 123, c -> 456)
With the notable difference that now we are not pulling anymore a dangerously broad conversion into cope.
UPDATE: In response to the comment below "to go further in my need, arg can be a single value or a list, and I have awful Some(List(sth)) in my client code today"
You can add another conversion to wrap individual parameters into one element list:
implicit def autoWrapAsList[T]( value: T ): OptionalArg[List[T]] = SomeArg(List(value))
Then say that your method expects an optional list like this:
def build1(a: OptionalArg[List[Int]] = NoArg, b: OptionalArg[Int] = NoArg, c: OptionalArg[Int] = NoArg): Map[String, Int] = {
val optPairs = List(a.map("a" -> _.sum),
b.map("b" -> _),
c.map("c" -> _))
val pairs = optPairs.flatten
Map(pairs: _*)
}
You can now either pass an individual element or a list (or just like before, no argument at all):
scala> Builder.build1( a = 123, c = 456 )
res6: Map[String,Int] = Map(a -> 123, c -> 456)
scala> Builder.build1( a = List(1,2,3), c = 456 )
res7: Map[String,Int] = Map(a -> 6, c -> 456)
scala> Builder.build1( c = 456 )
res8: Map[String,Int] = Map(c -> 456)
One last warning: even though we have defined our very own "option" class, it is still true that you should always use implicit conversions with some care,
so take some time to balance whether the convenience is worth the risk in your use case.
Let's say I have this example case class
case class Test(key1: Int, key2: String, key3: String)
And I have a map
myMap = Map("k1" -> 1, "k2" -> "val2", "k3" -> "val3")
I need to convert this map to my case class in several places of the code, something like this:
myMap.asInstanceOf[Test]
What would be the easiest way of doing that? Can I somehow use implicit for this?
Two ways of doing this elegantly. The first is to use an unapply, the second to use an implicit class (2.10+) with a type class to do the conversion for you.
1) The unapply is the simplest and most straight forward way to write such a conversion. It does not do any "magic" and can readily be found if using an IDE. Do note, doing this sort of thing can clutter your companion object and cause your code to sprout dependencies in places you might not want:
object MyClass{
def unapply(values: Map[String,String]) = try{
Some(MyClass(values("key").toInteger, values("next").toFloat))
} catch{
case NonFatal(ex) => None
}
}
Which could be used like this:
val MyClass(myInstance) = myMap
be careful, as it would throw an exception if not matched completely.
2) Doing an implicit class with a type class creates more boilerplate for you but also allows a lot of room to expand the same pattern to apply to other case classes:
implicit class Map2Class(values: Map[String,String]){
def convert[A](implicit mapper: MapConvert[A]) = mapper conv (values)
}
trait MapConvert[A]{
def conv(values: Map[String,String]): A
}
and as an example you'd do something like this:
object MyObject{
implicit val new MapConvert[MyObject]{
def conv(values: Map[String, String]) = MyObject(values("key").toInt, values("foo").toFloat)
}
}
which could then be used just as you had described above:
val myInstance = myMap.convert[MyObject]
throwing an exception if no conversion could be made. Using this pattern converting between a Map[String, String] to any object would require just another implicit (and that implicit to be in scope.)
Here is an alternative non-boilerplate method that uses Scala reflection (Scala 2.10 and above) and doesn't require a separately compiled module:
import org.specs2.mutable.Specification
import scala.reflect._
import scala.reflect.runtime.universe._
case class Test(t: String, ot: Option[String])
package object ccFromMap {
def fromMap[T: TypeTag: ClassTag](m: Map[String,_]) = {
val rm = runtimeMirror(classTag[T].runtimeClass.getClassLoader)
val classTest = typeOf[T].typeSymbol.asClass
val classMirror = rm.reflectClass(classTest)
val constructor = typeOf[T].decl(termNames.CONSTRUCTOR).asMethod
val constructorMirror = classMirror.reflectConstructor(constructor)
val constructorArgs = constructor.paramLists.flatten.map( (param: Symbol) => {
val paramName = param.name.toString
if(param.typeSignature <:< typeOf[Option[Any]])
m.get(paramName)
else
m.get(paramName).getOrElse(throw new IllegalArgumentException("Map is missing required parameter named " + paramName))
})
constructorMirror(constructorArgs:_*).asInstanceOf[T]
}
}
class CaseClassFromMapSpec extends Specification {
"case class" should {
"be constructable from a Map" in {
import ccFromMap._
fromMap[Test](Map("t" -> "test", "ot" -> "test2")) === Test("test", Some("test2"))
fromMap[Test](Map("t" -> "test")) === Test("test", None)
}
}
}
Jonathan Chow implements a Scala macro (designed for Scala 2.11) that generalizes this behavior and eliminates the boilerplate.
http://blog.echo.sh/post/65955606729/exploring-scala-macros-map-to-case-class-conversion
import scala.reflect.macros.Context
trait Mappable[T] {
def toMap(t: T): Map[String, Any]
def fromMap(map: Map[String, Any]): T
}
object Mappable {
implicit def materializeMappable[T]: Mappable[T] = macro materializeMappableImpl[T]
def materializeMappableImpl[T: c.WeakTypeTag](c: Context): c.Expr[Mappable[T]] = {
import c.universe._
val tpe = weakTypeOf[T]
val companion = tpe.typeSymbol.companionSymbol
val fields = tpe.declarations.collectFirst {
case m: MethodSymbol if m.isPrimaryConstructor ⇒ m
}.get.paramss.head
val (toMapParams, fromMapParams) = fields.map { field ⇒
val name = field.name
val decoded = name.decoded
val returnType = tpe.declaration(name).typeSignature
(q"$decoded → t.$name", q"map($decoded).asInstanceOf[$returnType]")
}.unzip
c.Expr[Mappable[T]] { q"""
new Mappable[$tpe] {
def toMap(t: $tpe): Map[String, Any] = Map(..$toMapParams)
def fromMap(map: Map[String, Any]): $tpe = $companion(..$fromMapParams)
}
""" }
}
}
This works well for me,if you use jackson for scala:
def from[T](map: Map[String, Any])(implicit m: Manifest[T]): T = {
val mapper = new ObjectMapper() with ScalaObjectMapper
mapper.convertValue(map)
}
Reference from:Convert a Map<String, String> to a POJO
I don't love this code, but I suppose this is possible if you can get the map values into a tuple and then use the tupled constructor for your case class. That would look something like this:
val myMap = Map("k1" -> 1, "k2" -> "val2", "k3" -> "val3")
val params = Some(myMap.map(_._2).toList).flatMap{
case List(a:Int,b:String,c:String) => Some((a,b,c))
case other => None
}
val myCaseClass = params.map(Test.tupled(_))
println(myCaseClass)
You have to be careful to make sure the list of values is exactly 3 elements and that they are the correct types. If not, you end up with a None instead. Like I said, not great, but it shows that it is possible.
commons.mapper.Mappers.mapToBean[CaseClassBean](map)
Details: https://github.com/hank-whu/common4s
Here's an update to Jonathon's answer for Scala 3 (which no longer has TypeTag). Be aware that this won't work for case classes nested inside of other classes. But for top-level case classes it seems to work fine.
import scala.reflect.ClassTag
object Reflect:
def fromMap[T <: Product : ClassTag](m: Map[String, ?]): T =
val classTag = implicitly[ClassTag[T]]
val constructor = classTag.runtimeClass.getDeclaredConstructors.head
val constructorArgs = constructor.getParameters()
.map { param =>
val paramName = param.getName
if (param.getType == classOf[Option[_]])
m.get(paramName)
else
m.get(paramName)
.getOrElse(throw new IllegalArgumentException(s"Missing required parameter: $paramName"))
}
constructor.newInstance(constructorArgs: _*).asInstanceOf[T]
And a test for the above:
case class Foo(a: String, b: Int, c: Option[String] = None)
case class Bar(a: String, b: Int, c: Option[Foo])
class ReflectSuite extends munit.FunSuite:
test("fromMap") {
val m = Map("a" -> "hello", "b" -> 42, "c" -> "world")
val foo = Reflect.fromMap[Foo](m)
assertEquals(foo, Foo("hello", 42, Some("world")))
val n = Map("a" -> "hello", "b" -> 43)
val foo2 = Reflect.fromMap[Foo](n)
assertEquals(foo2, Foo("hello", 43))
val o = Map("a" -> "yo", "b" -> 44, "c" -> foo)
val bar = Reflect.fromMap[Bar](o)
assertEquals(bar, Bar("yo", 44, Some(foo)))
}
test("fromMap should fail when required parameter is missing") {
val m = Map("a" -> "hello", "c" -> "world")
intercept[java.lang.IllegalArgumentException] {
Reflect.fromMap[Foo](m)
}
}
I create an ObservableMap, and a subscriber that just prints any events it receives (taken from here):
class MyMap extends HashMap[Int,Int] with ObservableMap[Int,Int]
class MySub extends Subscriber[Message[(Int,Int)] with Undoable, ObservableMap[Int, Int]] {
def notify(pub: ObservableMap[Int, Int], evt: Message[(Int, Int)] with Undoable) {
println(evt)
}
}
val map = new MyMap
map.subscribe(new MySub)
Using +=, ++=, and -= work as expected:
scala> map += 1 -> 1
Include(NoLo,(1,1))
res5: map.type = Map(1 -> 1)
scala> map ++= Map(2 -> 4, 3 -> 9)
Include(NoLo,(3,9))
Include(NoLo,(2,4))
res6: map.type = Map(3 -> 9, 1 -> 1, 2 -> 4)
scala> map -= 1
Remove(NoLo,(1,1))
res7: map.type = Map(3 -> 9, 2 -> 4)
But update doesn't work:
scala> map(4) = 16
scala> map
res9: MyMap = Map(3 -> 9, 4 -> 16, 2 -> 4)
Why? It looks like ObservableMap overrides +=, -=, and clear. Both ++= and update appear to be implemented in terms of += (by Growable and MapLike respectably), so why does it work on one but not the other?
The mutable version of HashMap calls update which in turn calls put, which does not call += so the observable += method doesn't get called. I'm using scala 2.9.1, but this should be the same for 2.8 on.
From HashMap:
override def put(key: A, value: B): Option[B] = {
val e = findEntry(key)
if (e == null) { addEntry(new Entry(key, value)); None }
else { val v = e.value; e.value = value; Some(v) }
}
override def update(key: A, value: B): Unit = put(key, value)
def += (kv: (A, B)): this.type = {
val e = findEntry(kv._1)
if (e == null) addEntry(new Entry(kv._1, kv._2))
else e.value = kv._2
this
}