Caching implicit convertion: map.getOrElseUpdate throws unexpected exception

114 views
Skip to first unread message

Bruno Woltzenlogel Paleo

unread,
Aug 2, 2012, 5:33:04 PM8/2/12
to scala...@googlegroups.com
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.implicitConversions
  import 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 expected

  println(intToString(2).startsWith("whatever")) 


// But when intToString is called implicitly, as in the line below, an exception is thrown

  println(3.startsWith("whatever")) // But here, surprisingly, an exception is thrown

   



The exception message is:

java.lang.ExceptionInInitializerError
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)
Caused by: java.util.NoSuchElementException: key not found: 3
at 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: 1
at 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


Sonnenschein

unread,
Aug 3, 2012, 4:49:50 AM8/3/12
to scala...@googlegroups.com
Hi Bruno,

just don't declare val cache as implicit.

Peter

Bruno Woltzenlogel Paleo

unread,
Aug 3, 2012, 7:48:16 AM8/3/12
to Paul Phillips, scala-l...@googlegroups.com, scala...@googlegroups.com
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 + " !" ) 

That is why I forgot the "implicit" modifier preceding "val cache = ...". 

This original version also throws the same exception, by the way.

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.

Yes. If I had read the exception message more carefully, I might have figured this out. I didn't think about this possibility, because I wrongly thought that a "val" couldn't be used as an implicit conversion, but only as an implicit argument, and because I forgot that Map extends Function1.

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.

Thanks for your answer, Paul. Now that I understand what is happening, I will manage to work around the ambiguity.

Best regards,

Bruno
Reply all
Reply to author
Forward
0 new messages