Tuesday, December 4, 2007

Improving code using more Scala features

A couple of days ago Eric Willigers gave me some nice feedback on how to improve the code of the quick fix created for the previous post.

Before modifying the document we are required to lock it and then release it. I did it like this:


...
doc.atomicLock
doc.replace(ifRange.getStart,ifRange.getLength,resultingStatement,null)
doc.atomicUnlock
...


However this is not safe since the replace method could throw a BadLocationException. One of the things we could do is to add a try/catch/finally block. However Eric pointed me to a much nicer way to do , by using two techniques called:



The Loan pattern is a way to ensure resource disposal when the control leaves some scope. For this propose closures are used.

The Pimp my library pattern, based on a Martin Odersky’s article, provides a way to extend existing class libraries with new operations without recompiling. New operations are added by defining a wrapper for specific classes. In this case we need to apply the Loan pattern to the BaseDocument class


class RichDocument(doc : BaseDocument) {
def withLock[T](f : BaseDocument => T ) = {
try {
doc.atomicLock()

f(doc)

} catch {
case ble : BadLocationException =>
Exceptions.printStackTrace(ble)
} finally {
doc.atomicUnlock()
}
}
}


This class defines our wrapper, the f is an anonymous function with code that will modify the document. Now we can add this functionality to the BaseDocument class by using the following declaration:


object DocumentImplicits {
implicit def toRichDocument(doc : BaseDocument) = new RichDocument(doc)
}



Putting using these definitions in the fix code leaves the code like this:


...
doc.withLock(_.replace(ifRange.getStart,
ifRange.getLength,
resultingStatement,
null))
...


The "_.replace ..." syntax is interesting way of specifying an anonymous function with one argument. For more information on this see Placeholder Syntax for Anonymous Functions in section 6.23 of the Scala Language Specification.


Another thing that could be done using this pattern is to eliminate direct uses of the AstUtilities class. By doing this we can add the following definitions:


object AstNodeImplicits {
implicit def toRichAstNode(node : Node) = new RichAstNode(node)
}

class RichAstNode(node : Node) {
def getRange() = AstUtilities.getRange(node)
}


Before using this implicits we need to import them:


import org.langexplr.nbhints.utils.DocumentImplicits.toRichDocument
import org.langexplr.nbhints.utils.AstNodeImplicits.toRichAstNode


The final code for the fix method is the following:


def implement() = {
val doc = info.getDocument.asInstanceOf[BaseDocument]
val conditionRange = condition.getRange()
val conditionText = getConditionText(doc,conditionRange)
val statementRange = statement.getRange()
val statementText = doc.getText(statementRange.getStart,
statementRange.getLength)
val ifRange = ifNode.getRange()

val resultingStatement = statementText + getModifierText + conditionText

doc.withLock(_.replace(ifRange.getStart,
ifRange.getLength,
resultingStatement,
null))
}


Code for this example can be found here (Now updated to the released Netbeans 6.0 ) .