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"?

2
  • You may want to call your method textOption, in the same style as a Seq's head and headOption methods. Commented Oct 12, 2011 at 12:09
  • Sounds good. I'll change my code. Updating question also. Commented Oct 12, 2011 at 12:16

2 Answers 2

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.
Sign up to request clarification or add additional context in comments.

4 Comments

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.
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-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.
Forgot about that. In my case, the XML file is small (configuration file), so that will have almost no performance impact.
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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.