Converting an Option[A] to an Option[B]

While working on a Scala hobby project, I wanted to convert an Option[A] into an Option[B]. I’ve noticed that it’s often straightforward to come up with an ugly way to do things in Scala, but with a little more thought it’s almost always possible to find a much more elegant and concise way to do things. I had initially written the following:

def intersect(ray: Ray): Option[Intersection] = {
  val opt = shape intersect ray    // Returns an Option[(DifferentialGeometry, Double)]
  if (opt.isEmpty) None else {
    val (dg, t) = opt.get
    Some(new Intersection(dg, t, this))
  }
}

This code is part of my Scala ray tracer (which isn’t yet publicly available – at the moment it’s too incomplete). It calls the intersect method on a shape, which returns an Option containing two values: a DifferentialGeometry object that contains information about the intersection point on the surface of the shape, and a Double which is the distance of the intersection point along the ray.

The intersect method above is part of class Primitive, which represents an object in the scene – something that has a shape, a material and other properties. This method converts the Option[(DifferentialGeometry, Double)] into an Option[Intersection] – class Intersection represents an intersection between a primitive and a ray.

I wasn’t satisfied with the code above, and after looking at the Scala API documentation I discovered that it can be written much more concisely by using the collect method of class Option, like this:

def intersect(ray: Ray): Option[Intersection] =
  shape intersect ray collect { case (dg, t) => new Intersection(dg, t, this) }

The collect method takes a partial function as an argument. It returns None for inputs for which the partial function isn’t defined, and a Some(...) for inputs for which the partial function is defined. The partial function can be written as { case value => ... }. By writing the code this way, I don’t have to deal with checking, unpacking and packing the Option objects myself, which makes the code nice and clean.

edit: Using map instead of collect is a little bit more efficient, as Brian Howard points out. See the comments for details.

2 Comments

  1. Since your function applies to _all_ of the `Some(…)` values coming out of `shape intersect ray`, you could do this more efficiently (no need to check whether the function is defined) with `map` instead of `collect`. Or, you could use the `for` syntax (which is equivalent):

    for ((dg, t) <- shape intersect ray) yield new Intersection(dg, t, this)

    • Thanks Brian!

      You’re right, you can also replace collect by map. I’ve tried it out in my ray tracer, and also using the for syntax. There’s no noticeable performance improvement when I do this, though. Looking at the implementation of map and collect in class Option:

      def map[B](f: A => B): Option[B] = 
        if (isEmpty) None else Some(f(this.get))
      
      def collect[B](pf: PartialFunction[A, B]): Option[B] =
        if (!isEmpty && pf.isDefinedAt(this.get)) Some(pf(this.get)) else None  
      

      There is indeed an extra condition in the if-statement in collect, so in principle it’s less efficient.

      I don’t like the for syntax for this; when I see a for, I’d expect a loop, but there’s not really a loop here.

Comments are closed.