Creating a class instance from a map of constructor parameter values - scala

Say I have a following class:
case class Mock(id: Int, pty1: String, pty2: String)
How can I instantiate it from a following map dynamically?
val params = Map("id" → 234, "pty1" → "asdf", "pty2" → "asdf")

LOL ) Found out that I had a solution already implemented in my lib. Requires Scala 2.10.
def instantiate[T <: AnyRef : Manifest](params: Map[String, Any]): T = {
instantiate(Mirror.classToType(manifest[T].erasure), params).asInstanceOf[T]
}
def instantiate(tpe: Mirror.Type, params: Map[String, Any]) = {
val p = constructorParams(tpe, params)
require(
params.size == p.size &&
p.forall(p => params.contains(p.nameString)),
"Params map `" + params + "` doesn't match `" + p + "`"
)
Option(Mirror.typeToJavaClass(tpe).getConstructor(p.map(p => Mirror.typeToJavaClass(p.tpe)): _*))
.getOrElse(throw new RuntimeException("No appropriate constructor of `" + tpe + "` found"))
.newInstance(p.map(p => params(p.nameString).asInstanceOf[Object]): _*)
}
private def constructorParams(tpe: Mirror.Type, params: Map[String, Any]) = {
tpe.members.find(_.isConstructor).get.paramss(0)
}

I don't think you can in a determistic way, since the names of the parameters are not part of the byte code and therefor at run time there is no way to know which String argument comes first and which second.

Related

Scala: Accessing/casting entries from Map[String, Any] or better alternative

With pattern matching I am extracting attributes from an AST and saving them in a Map[String, Any], because they can be Strings, Integers, Lists etc. Now I want to use the attributes in a case class. For getting the elements I wrote this method:
def getAttr(attr: Map[String, Any], key : String):Any = {
val optElem = attr.get(key) match {
case Some(elem) => elem
case _ => throw new Exception("Required Attribute " + key + " not found")
}
}
Because I always know what type every attribute value is I want to use the value like this:
case class Object1(id: String, name: String)
Object1("o1", getAttr(attrMap, "name").asInstanceOf[String])
But I get the error "scala.runtime.BoxedUnit cannot be cast to java.lang.String"
What am I doing wrong? Or is there a better way to collect and use my attributes?
Your implementation of getAttr has type Unit since you return result of assignment of value to optElem
To fix:
def getAttr(attr: Map[String, Any], key : String):Any = {
attr.get(key) match {
case Some(elem) => elem
case _ => throw new Exception("Required Attribute " + key + " not found")
}
}
As addition to #Nyavro's absolutely correct answer: to avoid calling asInstanceOf every time you use getAttr, you can add type parameter to it:
def getAttr[R](attr: Map[String, Any], key: String): R = {
val optElem = attr.get(key) match {
case Some(elem) => elem
case _ => throw new Exception("Required Attribute " + key + " not found")
}
optElem.asInstanceOf[R]
}
And then just
Object1("o1", getAttr(attrMap, "name"))

Crashing the compiler with a "MatchError: AnyRef" when I call my scala macro

Edit: I've fixed the problem - I was incorrectly calling .map(f => f.typeSignature.asInstanceOf[TypeRef].args.head) on recursiveOpt, which meant that field.name was giving me the wrong field name in my copy method. I've removed the map and everything is working now.
I am writing a macro that will create a map of update methods for a case class, e.g.
case class Inner(innerStr: String)
case class Outer(outerStr: String, inner: Inner, innerOpt: Option[Inner])
should produce an update map for Outer that is something like
val innerMap = Map("innerStr" -> {(json: JsValue) => Try{(inner: Inner) => inner.copy(innerStr = json)}})
val outerMap = Map("outerStr" -> {(json: JsValue) => Try{(outer: Outer) => outer.copy(outerStr = json)}},
"inner.innerStr" -> {(json: JsValue) => Try{(outer: Outer) => innerMap.get("innerStr").get(json).flatMap(update => outer.copy(inner = update(outer.inner)))},
"innerOpt.innerStr" -> {(json: JsValue) => Try{(outer: Outer) => innerMap.get("innerStr").get(json).flatMap(update => outer.copy(inner = outer.inner.map(inner => update(inner))))})
which would then be called like
val oldOuter = Outer("str", Inner("str"), Some(Inner("str")))
val updatedOuter = outerMap.get("inner.innerStr").get(JsString("newStr")).get(oldOuter)
The idea is that given a json kv pair, I can retrieve the appropriate update method from the map using the key and then apply the update using the value, using implicit conversions to convert from the json value to the appropriate type.
My macro is working for the case of a flat case class, e.g. Inner(innerStr: String), and for a nested case class, e.g. Outer(outerStr: String, inner: Inner). However, the case of the nested option case class, Outer(outerStr: String, innerOpt: Option[Inner]), is crashing the compiler. I'm not sure if I'm doing something disastrously wrong, or if there's a bug in the compiler, or third option. This was done using the Scala 2.11.0-M7 REPL
Below is my code - I'm constructing a Map that accepts String input instead of JsValue input so that I don't need to import the play framework into my REPL. The blacklist filters out fields that should not be in the update map (e.g. one of the case classes we're applying this to has fields like "crypted_password" and "salt" that should never be updated via json sent in through a REST route). baseMethods constructs the key -> method tuples for the flat case, recursiveMethods constructs the key-method tuples for the nested case, and recursiveOptMethods constructs the key-value tuples for the nested option case; at the bottom of the macro these are all merged into a flat sequence and a placed in a Map.
I've tested the code in the recursiveOptMethods quasiquotes to ensure that I'm constructing a properly typed sequence of tuples and haven't found an error (also, this code is extremely similar to the recursiveMethods quasiquotes, which are functioning correctly), and I've tested the code that constructs the base, recursive, and recursiveOpt sequences of symbols and they seem to be doing their job.
Any help as to why I'm crashing the compiler would be greatly appreciated.
import scala.language.experimental.macros
def copyTestImpl[T: c.WeakTypeTag](c: scala.reflect.macros.Context)(blacklist: c.Expr[String]*): c.Expr[Map[String, (String) => scala.util.Try[(T) => T]]] = {
import c.universe._
val blacklistList: Seq[String] = blacklist.map(e => c.eval(c.Expr[String](c.resetAllAttrs(e.tree))))
def isCaseClass(tpe: Type): Boolean = tpe.typeSymbol.isClass && tpe.typeSymbol.asClass.isCaseClass
def isCaseClassOpt(tpe: Type): Boolean = tpe.typeSymbol.name.decoded == "Option" && isCaseClass(tpe.asInstanceOf[TypeRef].args.head)
def rec(tpe: Type): c.Expr[Map[String, (String) => scala.util.Try[(T) => T]]] = {
val typeName = tpe.typeSymbol.name.decoded
val fields = tpe.declarations.collectFirst {
case m: MethodSymbol if m.isPrimaryConstructor => m
}.get.paramss.head.filterNot(field => blacklistList.contains(typeName + "." + field.name.decoded))
val recursive = fields.filter(f => isCaseClass(f.typeSignature))
val recursiveOpt = fields.filter(f => isCaseClassOpt(f.typeSignature))
val base = fields.filterNot(f => isCaseClass(f.typeSignature) || isCaseClassOpt(f.typeSignature))
val recursiveMethods = recursive.map {
field => {
val fieldName = field.name
val fieldNameDecoded = fieldName.decoded
val map = rec(field.typeSignature)
q"""{
val innerMap = $map
innerMap.toSeq.map(tuple => ($fieldNameDecoded + "." + tuple._1) -> {
(str: String) => {
val innerUpdate = tuple._2(str)
innerUpdate.map(innerUpdate => (outer: $tpe) => outer.copy($fieldName = innerUpdate(outer.$fieldName)))
}
})}"""
}}
val recursiveOptMethods = recursiveOpt.map {
field => {
val fieldName = field.name
val fieldNameDecoded = fieldName.decoded
val map = rec(field.typeSignature.asInstanceOf[TypeRef].args.head)
q"""{
val innerMap = $map
innerMap.toSeq.map(tuple => ($fieldNameDecoded + "." + tuple._1) -> {
(str: String) => {
val innerUpdate = tuple._2(str)
innerUpdate.map(innerUpdate => (outer: $tpe) => outer.copy($fieldName = (outer.$fieldName).map(inner => innerUpdate(inner))))
}
})}"""
}}
val baseMethods = base.map {
field => {
val fieldName = field.name
val fieldNameDecoded = fieldName.decoded
val fieldType = field.typeSignature
val fieldTypeName = fieldType.toString
q"""{
$fieldNameDecoded -> {
(str: String) => scala.util.Try {
val x: $fieldType = str
(t: $tpe) => t.copy($fieldName = x)
}.recoverWith {
case e: Exception => scala.util.Failure(new IllegalArgumentException("Failed to parse " + str + " as " + $typeName + "." + $fieldNameDecoded + ": " + $fieldTypeName))
}
}}"""
}}
c.Expr[Map[String, (String) => scala.util.Try[(T) => T]]] {
q"""{ Map((List(..$recursiveMethods).flatten ++ List(..$recursiveOptMethods).flatten ++ List(..$baseMethods)):_*) }"""
}
}
rec(weakTypeOf[T])
}
def copyTest[T](blacklist: String*) = macro copyTestImpl[T]
And the top and bottom of my error from the 2.11.0-M7 REPL when calling copyTest[Outer]() (where Outer has an Option[Inner] field)
scala> copyTest[Outer]()
scala.MatchError: AnyRef
with Product
with Serializable {
val innerStr: String
private[this] val innerStr: String
def <init>(innerStr: String): Inner
def copy(innerStr: String): Inner
def copy$default$1: String #scala.annotation.unchecked.uncheckedVariance
override def productPrefix: String
def productArity: Int
def productElement(x$1: Int): Any
override def productIterator: Iterator[Any]
def canEqual(x$1: Any): Boolean
override def hashCode(): Int
override def toString(): String
override def equals(x$1: Any): Boolean
} (of class scala.reflect.internal.Types$ClassInfoType)
at scala.reflect.internal.Variances$class.inType$1(Variances.scala:181)
at scala.reflect.internal.Variances$$anonfun$inArgs$1$1.apply(Variances.scala:176)
at scala.reflect.internal.Variances$$anonfun$inArgs$1$1.apply(Variances.scala:176)
at scala.reflect.internal.util.Collections$class.map2(Collections.scala:55)
at scala.reflect.internal.SymbolTable.map2(SymbolTable.scala:14)
at scala.reflect.internal.Variances$class.inArgs$1(Variances.scala:176)
at scala.reflect.internal.Variances$class.inType$1(Variances.scala:189)
at scala.reflect.internal.Variances$$anonfun$inArgs$1$1.apply(Variances.scala:176)
at scala.reflect.internal.Variances$$anonfun$inArgs$1$1.apply(Variances.scala:176)
at scala.reflect.internal.util.Collections$class.map2(Collections.scala:55)
at scala.reflect.internal.SymbolTable.map2(SymbolTable.scala:14)
at scala.tools.nsc.typechecker.Analyzer$typerFactory$$anon$3.run(Analyzer.scala:93)
at scala.tools.nsc.Global$Run.compileUnitsInternal(Global.scala:1603)
at scala.tools.nsc.Global$Run.compileUnits(Global.scala:1588)
at scala.tools.nsc.Global$Run.compileSources(Global.scala:1583)
at scala.tools.nsc.interpreter.IMain.compileSourcesKeepingRun(IMain.scala:387)
at scala.tools.nsc.interpreter.IMain$ReadEvalPrint.compileAndSaveRun(IMain.scala:816)
at scala.tools.nsc.interpreter.IMain$ReadEvalPrint.compile(IMain.scala:775)
at scala.tools.nsc.interpreter.IMain$Request.compile$lzycompute(IMain.scala:951)
at scala.tools.nsc.interpreter.IMain$Request.compile(IMain.scala:946)
at scala.tools.nsc.interpreter.IMain.compile(IMain.scala:530)
at scala.tools.nsc.interpreter.IMain.interpret(IMain.scala:518)
at scala.tools.nsc.interpreter.IMain.interpret(IMain.scala:516)
at scala.tools.nsc.interpreter.ILoop.reallyInterpret$1(ILoop.scala:748)
at scala.tools.nsc.interpreter.ILoop.interpretStartingWith(ILoop.scala:793)
at scala.tools.nsc.interpreter.ILoop.command(ILoop.scala:660)
at scala.tools.nsc.interpreter.ILoop.processLine(ILoop.scala:427)
at scala.tools.nsc.interpreter.ILoop.loop(ILoop.scala:444)
at scala.tools.nsc.interpreter.ILoop$$anonfun$process$1.apply$mcZ$sp(ILoop.scala:862)
at scala.tools.nsc.interpreter.ILoop$$anonfun$process$1.apply(ILoop.scala:848)
at scala.tools.nsc.interpreter.ILoop$$anonfun$process$1.apply(ILoop.scala:848)
at scala.reflect.internal.util.ScalaClassLoader$.savingContextLoader(ScalaClassLoader.scala:95)
at scala.tools.nsc.interpreter.ILoop.process(ILoop.scala:848)
at scala.tools.nsc.MainGenericRunner.runTarget$1(MainGenericRunner.scala:81)
at scala.tools.nsc.MainGenericRunner.process(MainGenericRunner.scala:94)
at scala.tools.nsc.MainGenericRunner$.main(MainGenericRunner.scala:103)
at scala.tools.nsc.MainGenericRunner.main(MainGenericRunner.scala)
That entry seems to have slain the compiler. Shall I replay
your session? I can re-run each line except the last one.
I found the problem - originally I had val recursiveOpt = fields.filter(f => isCaseClassOpt(f.typeSignature)).map(f => f.typeSignature.asInstanceOf[TypeRef].args.head), which meant that when I called field.name on the recursiveOpt fields I was getting the wrong name back.

scala custom map

I'm trying to implement a new type, Chunk, that is similar to a Map. Basically, a "Chunk" is either a mapping from String -> Chunk, or a string itself.
Eg it should be able to work like this:
val m = new Chunk("some sort of value") // value chunk
assert(m.getValue == "some sort of value")
val n = new Chunk("key" -> new Chunk("value"), // nested chunks
"key2" -> new Chunk("value2"))
assert(n("key").getValue == "value")
assert(n("key2").getValue == "value2")
I have this mostly working, except that I am a little confused by how the + operator works for immutable maps.
Here is what I have now:
class Chunk(_map: Map[String, Chunk], _value: Option[String]) extends Map[String, Chunk] {
def this(items: (String, Chunk)*) = this(items.toMap, None)
def this(k: String) = this(new HashMap[String, Chunk], Option(k))
def this(m: Map[String, Chunk]) = this(m, None)
def +[B1 >: Chunk](kv: (String, B1)) = throw new Exception(":( do not know how to make this work")
def -(k: String) = new Chunk(_map - k, _value)
def get(k: String) = _map.get(k)
def iterator = _map.iterator
def getValue = _value.get
def hasValue = _value.isDefined
override def toString() = {
if (hasValue) getValue
else "Chunk(" + (for ((k, v) <- this) yield k + " -> " + v.toString).mkString(", ") + ")"
}
def serialize: String = {
if (hasValue) getValue
else "{" + (for ((k, v) <- this) yield k + "=" + v.serialize).mkString("|") + "}"
}
}
object main extends App {
val m = new Chunk("message_info" -> new Chunk("message_type" -> new Chunk("boom")))
val n = m + ("c" -> new Chunk("boom2"))
}
Also, comments on whether in general this implementation is appropriate would be appreciated.
Thanks!
Edit: The algebraic data types solution is excellent, but there remains one issue.
def +[B1 >: Chunk](kv: (String, B1)) = Chunk(m + kv) // compiler hates this
def -(k: String) = Chunk(m - k) // compiler is pretty satisfied with this
The - operator here seems to work, but the + operator really wants me to return something of type B1 (I think)? It fails with the following issue:
overloaded method value apply with alternatives: (map: Map[String,Chunk])MapChunk <and> (elems: (String, Chunk)*)MapChunk cannot be applied to (scala.collection.immutable.Map[String,B1])
Edit2:
Xiefei answered this question -- extending map requires that I handle + with a supertype (B1) of Chunk, so in order to do this I have to have some implementation for that, so this will suffice:
def +[B1 >: Chunk](kv: (String, B1)) = m + kv
However, I don't ever really intend to use that one, instead, I will also include my implementation that returns a chunk as follows:
def +(kv: (String, Chunk)):Chunk = Chunk(m + kv)
How about an Algebraic data type approach?
abstract sealed class Chunk
case class MChunk(elems: (String, Chunk)*) extends Chunk with Map[String,Chunk] {
val m = Map[String, Chunk](elems:_*)
def +[B1 >: Chunk](kv: (String, B1)) = m + kv
def -(k: String) = m - k
def iterator = m.iterator
def get(s: String) = m.get(s)
}
case class SChunk(s: String) extends Chunk
// A 'Companion' object that provides 'constructors' and extractors..
object Chunk {
def apply(s: String) = SChunk(s)
def apply(elems: (String, Chunk)*) = MChunk(elems: _*)
// just a couple of ideas...
def unapply(sc: SChunk) = Option(sc).map(_.value)
def unapply(smc: (String, MChunk)) = smc match {
case (s, mc) => mc.get(s)
}
}
Which you can use like:
val simpleChunk = Chunk("a")
val nestedChunk = Chunk("b" -> Chunk("B"))
// Use extractors to get the values.
val Chunk(s) = simpleChunk // s will be the String "a"
val Chunk(c) = ("b" -> nestedChunk) // c will be a Chunk: Chunk("B")
val Chunk(c) = ("x" -> nestedChunk) // will throw a match error, because there's no "x"
// pattern matching:
("x" -> mc) match {
case Chunk(w) => Some(w)
case _ => None
}
The unapply extractors are just a suggestion; hopefully you can mess with this idea till you get what you want.
The way it's written, there's no way to enforce that it can't be both a Map and a String at the same time. I would be looking at capturing the value using Either and adding whatever convenience methods you require:
case class Chunk(value:Either[Map[String,Chunk],String]) {
...
}
That will also force you to think about what you really need to do in situations such as adding a key/value pair to a Chunk that represents a String.
Have you considered using composition instead of inheritance? So, instead of Chunk extending Map[String, Chunk] directly, just have Chunk internally keep an instance of Map[String, Chunk] and provide the extra methods that you need, and otherwise delegating to the internal map's methods.
def +(kv: (String, Chunk)):Chunk = new Chunk(_map + kv, _value)
override def +[B1 >: Chunk](kv: (String, B1)) = _map + kv
What you need is a new + method, and also implement the one declared in Map trait.

How save a TypeTag and then use it later to reattach the type to an Any (Scala 2.10)

I am trying to make custom heterogeneous lists and maps. Although there are examples around using Manifest, with Scala 2.10 they are deprecated and I should use TypeTags (or Classtags). In the case of maps it seems I can preserve the binding of an Any to a Type using (say) a tuple String->(TypeTag[ _ <: Any ], Any ).
My problem is how to get from the recovered TypeTag and an undefined T, to be able to return an instance of TypeTag.tpe - at the point in the code where I have
//** How do I use saved typeTag to define T here?**
As written, there are no compiler errors in method get, but T is set to Nothing and returns Some(Nothing). I would like my commented-out line to work:
case Some( x ) => // println( "Get 2*'pi'=" + x*2 ) where there is a complier message, "value * is not a member of Nothing".
I realise I could write more compactly, but as done, I can mouse-over in my IDE and follow step by step. There is a related question - Scala: What is a TypeTag and how do I use it? but it does not seem to go the 'last mile' - retagging an Any.
How to do this?
Here is the code I have so far:
import scala.reflect._
import scala.reflect.runtime.universe._
import collection.mutable.Map
object Test extends HMap {
def main( args: Array[ String ] ) {
var hmap = new HMap
hmap( "anInt" ) = 1
hmap( "pi" ) = 3.1416f
hmap( "c" ) = "hello"
// Test
val result = hmap.get( "pi" )
result match {
case Some( x ) =>
println( "Get 'pi'=" + x*2 )
case _ =>
}
}
}
class HMap {
private var coreMap =
Map.empty[ String, ( TypeTag[ _ <: Any ], Any ) ]
// Save the type tag with the value
def update[ T: TypeTag ]( key: String, value: T ) =
coreMap.put( key, ( typeTag[ T ], value ) )
override def toString = coreMap.toString
def get[ T: TypeTag ]( key: String ): Option[ T ] = {
val option = coreMap.get( key )
val result = option match {
case None => None
case Some( x ) => {
val typeTag = x._1; val value = x._2
println( "Matched Type = " +
typeTag.tpe + " Value=" + value )
// **** How do I use saved typeTag to define T here? ****
val v = value.asInstanceOf[ T ]
val s = Some( v )
println( "Returning " + s )
s
}
}
result
}
}
T is defined when you call the method get, you cant change it inside a function to another type. Compiler need information to get type information for T or you have to provide it explicitly:
def get[T](key: String) = m.get(key).map(_.asInstanceOf[T])
get[Int]("anInt")
If a key is typed, then T can be inferred:
class Key[T](name: String)
def get[T](key: Key[T]) = ...
get(Key[Int]("anInt"))
To check the type is correct when getting from map you can do what you did originally, save a type and a value:
val m = Map.empty[String, (Type, Any)]
def put[T: TypeTag](key: String, value: T) = m.put(key, (typeOf[T], value))
def get[T: TypeTag](key: String) = m.get(key) match {
case Some((t, v)) if t =:= typeOf[T] => Some(v.asInstanceOf[T])
case _ => None
}
Here is my own answer: I don't need TypeTags and I don't really need a keyed type or a tuple. Just simple generics seems to do. I don't need a match & case classes enumerating the possible values for Any. Just a simple map of name-value pairs. I then access it using
hmap.get Float .... println( "Get 'pi'=" + x * 2 )
It seems reasonable and very readable to just invoke: hmap.get Float . The types are not checked at compile time and so I will get runtime errors if I ask for the wrong type. If I do decide to filter the types there is a place to do it. I should check for castability and let Option return None if it can't be done and not Exception as it does now.
To me the above is neater & reads nicer than casting later since all the error handling can be in the get rather than using :
hmap.get("pi").asInstanceOf[Float].
Thanks for your help!
Here is the simpler code now:
import collection.mutable.Map
object Test extends HMap {
def main( args: Array[ String ] ) {
var hmap = new HMap
hmap( "anInt" ) = 1
hmap( "pi" ) = 3.1416f
hmap( "c" ) = "hello"
// Test
val result = hmap.get[ Float]( "pi" )
result match {
case Some( x ) =>
println( "Get 'pi'=" + x * 2 )
case _ => println("Not found")
}
}
}
class HMap {
private var coreMap =
Map.empty[ String, Any ]
// Save the key with the value
def update[ T ]( key: String, value: T ) =
coreMap.put( key, value )
def get[ T ]( key: String ): Option[ T ] = {
val option = coreMap.get( key )
option match {
case None => None
case Some( x ) => Some( x.asInstanceOf[ T ] )
}
}
}

How do I get rid of this type warning/error?

I have a script. It runs without warnings.
$ cat ~/tmp/so1.scala
import org.yaml.snakeyaml.Yaml
class JavaMapIteratorWrapper[K,V] (map: java.util.Map[K,V]) {
def foreach (f: Tuple2 [K, V] => Unit): Unit = {
val iter = map.entrySet.iterator
while (iter.hasNext) {
val entry = iter.next
f (entry.getKey, entry.getValue)
}
}
}
implicit def foreachJavaMap[K,V] (map: java.util.Map[K,V]): JavaMapIteratorWrapper[K,V] = new JavaMapIteratorWrapper[K,V](map)
val yaml = new Yaml;
(yaml load (io.Source.fromFile(argv(0)).mkString)) match {
case map: java.util.Map [_, _] => {
for (entry <- map) {
entry match {
case ("id", id: String) => System.out.println ("ID is " + id)
case (n: String, v: String) => System.out.println (n + " = " + v)
}
}
}
}
$ scala -unchecked -classpath jar/snakeyaml-1.7.jar ~/tmp/so1.scala eg/default.yaml
(program output as expected)
I'd like to extract the loop into its own function. So I try that.
$ cat ~/tmp/so2.scala
import org.yaml.snakeyaml.Yaml
class JavaMapIteratorWrapper[K,V] (map: java.util.Map[K,V]) {
def foreach (f: Tuple2 [K, V] => Unit): Unit = {
val iter = map.entrySet.iterator
while (iter.hasNext) {
val entry = iter.next
f (entry.getKey, entry.getValue)
}
}
}
implicit def foreachJavaMap[K,V] (map: java.util.Map[K,V]): JavaMapIteratorWrapper[K,V] = new JavaMapIteratorWrapper[K,V](map)
val processMap = (map: java.util.Map [_, _]) => {
for (entry <- map) { // line 16
entry match {
case ("id", id: String) => System.out.println ("ID is " + id)
case (n: String, v: String) => System.out.println (n + " = " + v)
}
}
}
val yaml = new Yaml;
(yaml load (io.Source.fromFile(argv(0)).mkString)) match {
case map: java.util.Map [_, _] => processMap (map)
}
$ scala -unchecked -classpath jar/snakeyaml-1.7.jar ~/tmp/so2.scala eg/default.yaml
(fragment of so2.scala):16: error: type mismatch;
found : map.type (with underlying type java.util.Map[_, _])
required: java.util.Map[_$1,_$2] where type _$2, type _$1
for (entry <- map) {
^
one error found
!!!
discarding <script preamble>
The loop being in its own function means it requires a more specific type. Okay.
I'll try with java.util.Map [AnyRef, AnyRef] instead of java.util.Map [_, _].
$ cat ~/tmp/so3.scala
import org.yaml.snakeyaml.Yaml
class JavaMapIteratorWrapper[K,V] (map: java.util.Map[K,V]) {
def foreach (f: Tuple2 [K, V] => Unit): Unit = {
val iter = map.entrySet.iterator
while (iter.hasNext) {
val entry = iter.next
f (entry.getKey, entry.getValue)
}
}
}
implicit def foreachJavaMap[K,V] (map: java.util.Map[K,V]): JavaMapIteratorWrapper[K,V] = new JavaMapIteratorWrapper[K,V](map)
val processMap = (map: java.util.Map [AnyRef, AnyRef]) => {
for (entry <- map) {
entry match {
case ("id", id: String) => System.out.println ("ID is " + id)
case (n: String, v: String) => System.out.println (n + " = " + v)
}
}
}
val yaml = new Yaml;
(yaml load (io.Source.fromFile(argv(0)).mkString)) match {
case map: java.util.Map [AnyRef, AnyRef] => processMap (map) // line 26
}
$ scala -unchecked -classpath jar/snakeyaml-1.7.jar ~/tmp/so3.scala eg/default.yaml
(fragment of so3.scala):26: warning: non variable type-argument AnyRef in type pattern is unchecked since it is eliminated by erasure
case map: java.util.Map [AnyRef, AnyRef] => processMap (map)
^
one warning found
!!!
discarding <script preamble>
(program output as expected)
So now it runs, but it gives me a warning. How do I eliminate that warning?
Notes:
org.yaml.snakeyaml.Yaml is written in Java, so I can't use type manifests. (Can I?)
My real program uses several Java libraries, so I want to be warned when I make possibly false assumptions about what types
I'm being given. But how do I tell the compiler "yes, I've checked this, it's correct, don't warn me about it again"?
I'm using scala 2.7.7 (because that's the version that's packaged with Ubuntu).
You could try removing your custom wrapper to start with. The (2.8.1) Scala standard library already includes a wrapper to use Java collection types more idiomatically, in scala.collection.JavaConverters. (note: the scala. prefix is not needed when importing this)
I'd also make processMap a method instead of a function, and add type params:
import collection.JavaConverters._
def processMap[K,V](map: Map[K, V]): Unit = {
for (entry <- map) {
entry match {
case ("id", id: String) => System.out.println ("ID is " + id)
case (n: String, v: String) => System.out.println (n + " = " + v)
}
}
}
val yaml = new Yaml
(yaml load (io.Source.fromFile(argv(0)).mkString)) match {
case map: java.util.Map[_, _] => processMap(map.asScala)
}
Note the asScala method on the second to last line...
When dealing with Java/Scala interop, it's generally a best practice to convert from Java to Scala collections at the earliest opportunity, and to convert back as late as possible.
You must be using Scala 2.7.X. If you use 2.8.1, your example with Map[_,_] works fine.
If you need to use 2.7.X, try converting your processMap value into a method:
def processMap[K,V] = (map: java.util.Map[K,V]) => {...}
That seemed to compile for me, but note that I "stubbed" the parts using the YAML library. I used:
val m1 = new java.util.HashMap[String,String]
m1.put("one", "1")
m1.put("id", "123")
m1.put("two", "2")
m1 match {
case map: java.util.Map [_, _] => processMap (map)
}