Hi!I would like to have an implicit conversion with caching of the results (in a mutable map). However, when called implicitly, the map's getOrElseUpdate throws an exception. The following is a simplification of my real code (in particular, in my real code the conversion is not between Int and String).import language.implicitConversionsimport collection.mutable.{Map => MMap}implicit val cache : MMap[Int, String] = MMap()implicit def intToString(p: Int): String = cache.getOrElseUpdate(p, "number " + p.toString + " !" )
// Everything works fine in the following line,// "false" is printed and (2 -> "number 2 !") is added to cache as expectedprintln(intToString(2).startsWith("whatever"))// But when intToString is called implicitly, as in the line below, an exception is thrownprintln(3.startsWith("whatever")) // But here, surprisingly, an exception is thrown
The exception message is:java.lang.ExceptionInInitializerErrorat at.logic.skeptik.Main.main(Main.scala)at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)at java.lang.reflect.Method.invoke(Method.java:597)Caused by: java.util.NoSuchElementException: key not found: 3at scala.collection.MapLike$class.default(MapLike.scala:228)at scala.collection.AbstractMap.default(Map.scala:58)at scala.collection.mutable.HashMap.apply(HashMap.scala:63)at at.logic.skeptik.Main$.<init>(Main.scala:55)at at.logic.skeptik.Main$.<clinit>(Main.scala)at at.logic.skeptik.Main.main(Main.scala)at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)at java.lang.reflect.Method.invoke(Method.java:597)java.lang.RuntimeException: Nonzero exit code: 1at scala.sys.package$.error(package.scala:27)Is this a bug of the Scala (2.10.0-M5) language? It seems to me that getOrElseUpdate tries something and catches an exception if the key is not found, but for some reason the exception is escaping the catch when the call to getOrElseUpdate is made from within an implicitly called implicit conversion...Or is this expected behavior? If so, what should I do to make getOrElseUpdate work also from within an implicit call?I would also be happy to hear any alternative idea for implementing an implicit converter that caches and reuses the conversions.Best regards,Bruno
Someone asked me recently why I'm against extending Function1 casually. In this we have a pretty good example. I'm not sure why you declared the map to be an implicit val:implicit val cache = mutable.Map[Int, String]()
implicit def intToString(p: Int)(implicit cache: Map[Int, String]): String = cache.getOrElseUpdate(p, "number " + p.toString + " !" )
So this line:println(3.startsWith("whatever"))Is using the cache directly to implicitly convert Int to String, i.e. it's calling cache.apply, not cache.getOrElseUpdate.
I imagine you didn't do that with the intention of placing a competing implicit conversion in scope, because your Map is also an Int => String. And for whatever reason it's not ambiguous - the val is preferred over the def I guess.
Someone asked me recently why I'm against extending Function1 casually. In this we have a pretty good example. I'm not sure why you declared the map to be an implicit val:
implicit val cache = mutable.Map[Int, String]()Originally (and also in my real code) "cache" was passed as an implicit argument to the implicit "intToString" conversion, as follows.implicit def intToString(p: Int)(implicit cache: Map[Int, String]): String = cache.getOrElseUpdate(p, "number " + p.toString + " !" )
I imagine you didn't do that with the intention of placing a competing implicit conversion in scope, because your Map is also an Int => String. And for whatever reason it's not ambiguous - the val is preferred over the def I guess.
This is what I still find strange. I wish I had gotten a compilation error informing me of the ambiguity.
Paul, the fact you're confused worries me. First because I can't find any val-vs-def precedence rule around (http://stackoverflow.com/questions/5598085/where-does-scala-look-for-implicits or ScalaReference); second because why would such a rule be useful?I guess the situation is sane just because Map[Int, String] <: Int => String, so that cache wins because it has a more specific type (a rule which usually _does_ make sense). Bruno, this seems enough to explain why you didn't get an error about ambiguity.
[Redirected to scala-language since I'm not on scala-user.]Someone asked me recently why I'm against extending Function1 casually. In this we have a pretty good example.
On Fri, Aug 3, 2012 at 3:45 PM, Paolo Giarrusso <p.gia...@gmail.com> wrote:Paul, the fact you're confused worries me. First because I can't find any val-vs-def precedence rule around (http://stackoverflow.com/questions/5598085/where-does-scala-look-for-implicits or ScalaReference); second because why would such a rule be useful?I guess the situation is sane just because Map[Int, String] <: Int => String, so that cache wins because it has a more specific type (a rule which usually _does_ make sense). Bruno, this seems enough to explain why you didn't get an error about ambiguity.It is a good explanation, but it doesn't seem to be the actual reason. In the following f1 and f2 are not ambiguous. The overload of g1 is ambiguous. I thought overloading rules were how one resolved the implicit, but if so, it's in some other way than the way I read it.
Instead, the fact that Map extends Function1 allows to reuse function-oriented abstractions on Map:
Can annotations refer to types?
Use CanBuildFrom and use the new .to[Function]?
Cheers,
V