Connecting...

W1siziisimnvbxbpbgvkx3rozw1lx2fzc2v0cy9zawduawz5lxrly2hub2xvz3kvanbnl2jhbm5lci1kzwzhdwx0lmpwzyjdxq

Is your Scala object always a singleton? By Jacek Kunicki

W1siziisijiwmtkvmdivmtgvmtevmzqvmdmvotcvcgv4zwxzlxbob3rvltqxmte5ns5qcgvnil0swyjwiiwidgh1bwiilci5mdb4otawxhuwmdnlil1d

Is your Scala object always a singleton?

This is the question that Senior Software Engineer at SoftwareMill, Jacek Kunicki aims to answer. Look at pattern matching, the anatomy of an object and the solution!

 

'You’re probably not surprised that the answer is it depends.

In this article I’m going to present a root cause analysis of a bug we encountered in one of our projects — the bottomline was having more than one instance of a Scala 'object', which was supposed to be a singleton.

 

The pattern matching

The story began with pattern matching on an ADT like:

1  sealed trait Notification
2
3  case class Email(to: String) extends Notification
4  case object Slack extends Notification
5
6  class Notifier {
7    def sendNotification(notification: Option[Notification]) = notification match {
8      case Some(Email(to)) => // send email
9      case Some(Slack) => // send message to Slack
10   }
11 }

It worked like a charm until, after introducing an apparently unrelated change, we started getting errors like:

scala.MatchError: Some(Slack) (of class scala.Some)

The error seemed a bit strange, since this was a really simple pattern matching which should just have worked. Also, the matching on 'Email' still worked fine. And since the 'Slack' object was supposed to be a singleton, we didn’t really have any clue how could it possibly not be matched.

But somehow it was — so let’s unveil some more facts.

 

The mapper

The only idea we came up with was that there had to be more than one instance of 'Slack' — which we were able to confirm by looking at the hashcodes in the debugger.

Initially, we suspected some classloading issue, but since the application was a standalone one and didn’t run inside any container, this was hardly possible. Checking the classloaders with the debugger confirmed that this was a red herring.

Now comes the key fact: the values received by 'Notifier.sendNotification' were not provided directly by our application code, but instead read from some configuration stored in MongoDB.

Additionally, we were using Morphia as the document-to-object mapper, which was responsible for providing the actual instances of the implementations of 'Notification'.

To complicate things even more, in our case the 'Notification' was a property of two different objects, say 'A' and 'B', so its instances could be a result of mapping documents from two different collections. Now the funny thing was that 'A.notification' was matched correctly, but 'B.notification' was not (or the other way around, but still only one of those worked fine while the other didn’t).

The obvious next step was to dig into the source code of Morphia and check how it mapped the documents to Scala objects.

The reflection

It turned out that Morphia uses Java reflection to instantiate the objects fetched from MongoDB:

1  private static Constructor getNoArgsConstructor(final Class type) {
2    try {
3      final Constructor constructor = type.getDeclaredConstructor();
4      constructor.setAccessible(true);
5      return constructor;
6    } catch (NoSuchMethodException e) {
7      throw new MappingException("No usable constructor for " + type.getName(), e);
8    }
9  }
10
11 public static Object createInst(final Class clazz) {
12   try {
13     return getNoArgsConstructor(clazz).newInstance();
14   } catch (...) {
15     //...
16   }
17 }

Since Morphia is a Java library, it has no way to be aware of something like a Scala 'object' — it just creates a new instance of whatever class it encounters.

Also, from the JVM perspective, an 'object' is just a class that, actually, has a private constructor, so nothing can stop you (or Morphia) from manually creating a new instance using reflection.

Let’s now digress a bit from the main story and see what a compiled 'object' actually looks like.

 

The anatomy of an object

If you compile the 'Notification' ADT, you’ll get a couple of resulting classes:

$ scalac Notifications.scala

$ ls -1 *.class
Email$.class
Email.class
Notification.class
Slack$.class
Slack.class

If you have never seen a compiled 'object' before, you may be surprised by the fact that the compiler not only generated 'Slack.class' (which you would probably expect), but also 'Slack$.class'.

Let’s inspect those two classes with 'javap' — a command line tool in the JDK that lets you disassemble class files:

$ javap -p Slack.class
Compiled from "Notifications.scala"
public final class Slack {
  public static java.lang.String toString();
  public static int hashCode();
  public static boolean canEqual(java.lang.Object);
  public static scala.collection.Iterator<java.lang.Object> productIterator();
  public static java.lang.Object productElement(int);
  public static int productArity();
  public static java.lang.String productPrefix();
}
$ javap -p Slack\$.class
Compiled from "Notifications.scala"
public final class Slack$ 
  implements  Notification,scala.Product,scala.Serializable {
  public static final Slack$ MODULE$;
  public static {};
  public java.lang.String productPrefix();
  public int productArity();
  public java.lang.Object productElement(int);
  public scala.collection.Iterator<java.lang.Object> productIterator();
  public boolean canEqual(java.lang.Object);
  public int hashCode();
  public java.lang.String toString();
  private java.lang.Object readResolve();
  private Slack$();
}

If you took a deeper dive into 'Slack.class' with 'javap -c' (which shows the actual bytecode), you would notice that this class is only a helper whose static methods delegate to the respective ones in 'Slack$'.

Now in the 'Slack$' class, at the very end, you can notice the actual private constructor that can’t be used with 'new' (since it’s private), but can still be called using reflection.

The other crucial part of the 'Slack$' class is the static 'MODULE$' field — which turns out to be the singleton instance created internally by calling the private constructor.

As long as you access the object in your Scala code using 'Slack', or in your Java code using 'Slack$.MODULE$' (which looks ugly but is the way to go), it remains a singleton. However, as you already know, nothing can prevent you from creating another instances using reflection.

Also, if you look deeper into 'Slack$' with 'javap -c', you will notice that the 'MODULE$' static field is initialized every time the private constructor is called:

private Slack$();
    Code:
       0: aload_0
       1: invokespecial #64                 // Method 
java/lang/Object."<init>":()V
       4: aload_0
       5: putstatic     #63                 // Field 
MODULE$:LSlack$;
       8: aload_0
       9: invokestatic  #68                 // InterfaceMethod 
scala/Product.$init$:(Lscala/Product;)V
      12: return

This is the reason why only some instances (to be precise — only the most recently created one) are matched and the others are not. Kudos to Kamil Rafałko for pointing this out.

 

The solution

Coming back to our case with reading the 'Notification's from MongoDB, it should now be clear why the pattern matching didn’t work: a new instance of 'Slack' was created every time the value was deserialized.

To fix it, we decided to write a custom 'TypeConverter' that Morphia would use when deserializing 'Slack' objects, which would always provide the singleton instance instead of creating a new one every time:

1  class NotificationConverter extends TypeConverter(classOf[Notification]) 
2        with SimpleValueConverter {
3
4    private lazy val entityCache = mapper.createEntityCache()
5  
6    override def encode(value: Any, optionalExtraInfo: MappedField): AnyRef =
7      Option(value).map(mapper.toDBObject).orNull
8  
9    override def decode(targetClass: Class[_], fromDBObject: Any, 
10                       optionalExtraInfo: MappedField): AnyRef =
11     Option(fromDBObject).map(_.asInstanceOf[DBObject]).map {
12       case dbObject if isSlack(dbObject) => Slack
13       case other => mapper.fromDBObject(targetClass, other, entityCache)
14     }.orNull
15 
16   private def isSlack(dbObject: DBObject) = 
17     dbObject.get(Mapper.CLASS_NAME_FIELDNAME) == Slack.getClass.getName
18 }
The idea behind the custom converter is to handle the special case of deserializing the 'Slack' value and falling back to default behavior in any other case.
 

The summary

Although under normal circumstances the 'object's in Scala are indeed singletons, this can no longer be the case when reflection comes into play. From the reflection perspective, an 'object' is nothing more than any other class with a private constructor.

Moreover, when there’s more than a single instance of an 'object', their behavior can become pretty weird — check the examples below and see for yourself.

 

The riddle

Can you tell what would be the result of running the code below? Try it in the Scala REPL or in Ammonite (which is a more powerful REPL with syntax highlighting). Is the output as you expected? Is it deterministic?
1  object Test
2
3  def test(t: Test.type) = t match {
4    case Test => true
5    case _ => false
6  }
7
8  val c = Test.getClass.getDeclaredConstructor()
9
10 val t1 = c.newInstance()
11 val t2 = c.newInstance()
12
13 t1 == Test
14 t2 == Test
15 t1 == t2
16
17 val t3 = c.newInstance()
18 val t4 = c.newInstance()
19
20 t3 == Test
21 t4 == Test
22 t3 == t4
23 
24 test(t1)
25 test(t2)
26 test(t3)
27 test(t4)
28 test(Test)

 

This article was written by Jacek Kunicki and posted originally on SoftwareMill Blog.