Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala None instance not == None

Tags:

scala

I've got an intermittent problem in Scala code where I'm working with values from an immutable map with String keys. Here's the basic code, including debug logging I added:

  val compStruct = subsq.comps get (ident)
  compStruct match {
    ...
    case None =>
      logger.info(s"Found None, of type ${compStruct.getClass.getName}, at position $position (ident $ident)")
      ...
    case x =>
      logger.info(s"Illegal structure of type ${x.getClass.getName} at position $position (ident $ident) - x == None is ${x == None}, x.getClass == None.getClass is ${x.getClass == None.getClass}, x.getClass.getName == None.getClass.getName (${None.getClass.getName}) is ${x.getClass.getName == None.getClass.getName}")
      ...
  }

the problem is that case x is sometimes taken when the value is actually a None, as shown by the (sanitized) debug output:

  INFO  ...: Found None, of type scala.None$, at position 3000 (ident XX)
  INFO  ...: Illegal structure of type scala.None$ at position 3200 (ident XX) - x == None is false, x.getClass == None.getClass is true, x.getClass.getName == None.getClass.getName (scala.None$) is true

(The first line is what I'd expect to happen and indeed does happen normally; the rest are the error case)

So if my logging is to be believed (and I haven't messed up my expression somehow) I've got a case where the map is returning x, where x is an instance of class scala.None$ (the same class scala.None$ as seen by the compiled code) but doesn't match the case None and x == None is false.

A classloading issue would be the obvious cause, but x.class == None.class seems to preclude that.

Added: As I suggested in the comments, I can reproduce the instances of None not matching with the following code:

object Test {
  def main(args: Array[String]): Unit = {
    val none1 = None
    val clas = this.getClass.getClassLoader.loadClass("scala.None$")
    val constr = clas.getDeclaredConstructors()(0)
    constr.setAccessible(true)
    val none2 = constr.newInstance()
    println(s"none1 == none2 is ${none1 == none2}")
    println(s"none1 == None is ${none1 == None}")
    println(s"none2 == None is ${none2 == None}")
  }
}

Which gives:

none1 == none2 is false
none1 == None is false
none2 == None is true

I don't think that has anything to do with what's happening in the application, though.

Added: I modified the actual None$ classfile to both print a message when the constructor executes and throw an exception if the None$.MODULE$ value is non-null when the constructor is called, and even moved the store to the static MODULE$ value to the static constructor block (the original code had this store in the constructor, which I think is technically a violation of JVM rules since the object is not considered initialized until after the constructor returns).

This does block the reflection call to the constructor (above code sample) that duplicated the symptoms of the problem, but doesn't change anything in the actual application. The value of None$.MODULE$ changes from one execution of the code to the next, even though the class remains the same (same System.identityHashCode), yet the constructor is only called once.

like image 304
Dennis Sosnoski Avatar asked Feb 26 '16 01:02

Dennis Sosnoski


People also ask

How do I know if Scala is none?

We can test whether an Option is Some or None using these following methods: isDefined – true if the object is Some. nonEmpty – true if the object is Some. isEmpty – true if the object is None.

What does ::: mean in Scala?

:: - adds an element at the beginning of a list and returns a list with the added element. ::: - concatenates two lists and returns the concatenated list.


1 Answers

Regarding your test: None is an object in Scala. When you define val none1 = None, you assign to none1 this object, which is supposed to be a singleton.

By using reflection, you are bypassing the private constructor and creating a new instance of None class. The == operator will only return true if the two pointers are pointing to the same object. You can verify the memory address of these objects by using System.identityHashCode(none1) and compare it.

Also, if you try to run your match against none1 in your object Test, you will run into a match error, as the second instance of None does not match either None or x.

I was able to reproduce your error. By running this code:

val a = Map("a" -> "b", "b" -> None)
a.get("b") match { 
   case None => print("None")
   case x => print("X") 
}  // Prints X
a.get("c") match { 
    case None => print("None")
    case x => print("X") 
} // Prints None

I know that this does not explains why it prints X, but at least you know when...

Because your HashMap has None values, it is a HashMap[String,java.io.Serializable] rather than HashMap[String,String]. And the call to get will return a java.io.Serializable rather than a String.

To solve your problem and get it to match when it is None, you can do:

case x if(x.isInstanceOf[None$]) => 
like image 153
Vinicius Miana Avatar answered Oct 25 '22 19:10

Vinicius Miana