Tool-friendly pattern matching?

Tool-friendly pattern matching

Pattern matching is one of the features we love in Scala. Is it tool-friendly?

Let’s look at a standard pattern match:

foo match {
  case bar@Bar if bar.isOk() => bar.doIt()
  case baz@Bar if baz.isOk() => baz.doIt()
  case _ => ???
} 

How tools would like to see the code?

foo match {
  case bar@Bar 
    if bar.isOk() => 
      bar.doIt()
 
  case baz@Bar 
    if baz.isOk() => 
      baz.doIt()
 
  case _ => ???
} 

The only difference is line breaking. It is not python so why does it matter? Tools like code coverage or debuggers usually use JDI and JDI is line-based - every line of the bytecode has its mapping to a line in the source code.

Let’s take a look at a case statement in byte code:

      18: instanceof    #15                 // class test/Bar
      21: ifeq          51
      24: iconst_1      
      25: istore_3      
      26: aload         5
      28: checkcast     #15                 // class test/Bar
      31: astore        4
      33: aload         4
      35: invokevirtual #20                 // Method test/Bar.isOk:()Z
      38: ifeq          51
      41: aload         4
      43: invokevirtual #23                 // Method test/Bar.doIt:()Ljava/lang/String;
      46: astore        6
      48: goto          70
      51: iload_3       
      52: ifeq          176
      55: aload         4
      57: invokevirtual #20                 // Method test/Bar.isOk:()Z
      60: ifeq          176
      63: aload         4
      65: invokevirtual #23                 // Method test/Bar.doIt:()Ljava/lang/String;
      68: astore        6
      70: aload         6
      72: astore_2      
      73: aload_1       
      74: astore        8
      76: aload         8
      78: instanceof    #15                 // class test/Bar
      81: ifeq          109
      84: aload         8
      86: checkcast     #15                 // class test/Bar
      89: astore        9
      91: aload         9
      93: invokevirtual #20                 // Method test/Bar.isOk:()Z
      96: ifeq          109
      99: aload         9
     101: invokevirtual #23                 // Method test/Bar.doIt:()Ljava/lang/String;
     104: astore        10
     106: goto          139
     109: aload         8

There is pretty much to do in one line assuming that we want to check if our case is hit. So we put a breakpoint in our case line. And what happens? It is hit every time a condition is being checked! Why? Checking a condition is also in our line - so tooling gets information “I was there”.

Conclusion

Let’s write cases this way:

foo match {
  case bar: Bar if bar.isOk() =>
    bar.doIt()
  case baz: Baz if baz.isOk() =>
    baz.doIt()
  case _ => ???
}  

Then when we place breakpoint inside case body - it will be hit only when the body is about to be invoked.