7

I have code that reads an XML file. Some of the attributes of elements that I need to process are optional. I am trying to use Option[T] to manage them. I have written the following to pimp the NodeSeq type returned by the \ Node operator:

class NodeSeqWrapper(nodeSeq: NodeSeq) {
  def textOption: Option[String] = {
    val text = nodeSeq.text
    if (text == null || text.length == 0) None else Some(text)
  }
}
implicit def nodeSeqWrapper(nodeSeq: NodeSeq): NodeSeqWrapper =
  new NodeSeqWrapper(nodeSeq)

and then call it like this:

(node \ "@attr").textOption.getOrElse("Some default value")

If the node has the "attr" attribute, this code gets it value. If it does not, the value "Some default value" is returned.

How can I improve this? Is there some way to fold the class definition into the implicit method? Is there a better way of getting "optional" attribute values? Am I using Option[T] "correctly"?

dbc
  • 104,963
  • 20
  • 228
  • 340
Ralph
  • 31,584
  • 38
  • 145
  • 282

2 Answers2

5

I would say you are doing it in a very idiomatic way, yes.

You can "fold the definitions" as follows:

implicit def enrichNodeSeq(nodeSeq: NodeSeq) = new AnyRef {
  def textOption : Option[String] = {
    val text = nodeSeq.text
    if (text == null || text.length == 0) None else Some(text)
  }
}

If you are always applying .getOrElse(...) on the result, you may also want to define a second version textOrElse(elze : String) : String:

implicit def enrichNodeSeq(nodeSeq: NodeSeq) = new AnyRef {
  def textOption : Option[String] = {
    val text = nodeSeq.text
    if (text == null || text.length == 0) None else Some(text)
  }

  def textOrElse(elze : String) : String = textOption.getOrElse(elze)
}

That will make things slightly more concise.

scala> (<b>Hello</b> : NodeSeq).textOrElse("No text found.")
resN: String = Hello
scala> (<br /> : NodeSeq).textOrElse("No text found.")
resM: String = No text found.
Philippe
  • 9,582
  • 4
  • 39
  • 59
  • That's what I was looking for. I tried using an anonymous class in my first attempt (`new {...}`) and it did not work. I also like the `textOrElse` method. Thanks. – Ralph Oct 12 '11 at 12:40
  • 2
    Beware that "folding the definitions" causes Java reflection to be used each time you call your extra methods. Declaring the extra class does not. – Jean-Philippe Pellet Oct 12 '11 at 12:45
  • @Jean-PhilippePellet Good point! I would suspect the JVM JIT compiler is pretty-good at handling reflective calls where the name of the method is given as a static string, though. – Philippe Oct 12 '11 at 12:47
  • Forgot about that. In my case, the XML file is small (configuration file), so that will have almost no performance impact. – Ralph Oct 12 '11 at 12:48
2

The answer can be improved on since Scala 2.10 with the introduction of implicit classes.

http://docs.scala-lang.org/overviews/core/implicit-classes.html

The example the op gave can be re-written using an implicit class like so:

object SomeExtensions {

  implicit class ExtendedNodeSeq(nodeSeq: NodeSeq) {
    def textOption: Option[String] = {
      val text = nodeSeq.text
      if (text == null || text.length == 0) None else Some(text)
    }
  }

}

Note that the example follows a couple of the restrictions for case classes:

  1. They must be defined inside of another trait/class/object.
  2. They may only take one non-implicit argument in their constructor.
be.outside
  • 91
  • 1
  • 2