Emerging Technologies Laboratory (ETL) Blog

Mar 10 2011 laufer

Successive Pattern Matching and the Happy Path

Before

Successive (nested) pattern matching with exhaustive matching at each point, 16 LOC in Scala.

    case req @ DELETE(Path(Seg("users" :: name :: "bookmarks" :: uri))) => {
      val uriString = uri mkString "/"
      logger.debug("DELETE /users/%s/bookmarks/%s".format(name, uriString))
      userRepository findByName name match {
        case Some(user) => req match {
    	  case BasicAuth(u, p) if verify(u, p, user) => {
    	    user.bookmarks.remove(uriString) match {
    	      case Some(_) => NoContent
    	      case _ => NotFound
    	    }
    	  }
    	  case _ => NotFound
        }
        case _ => NotFound
      }
    }

After

Successive happy-path style pattern matching with non-exhaustive patterns and MatchError exception handling, 9 LOC in Scala.

    case req @ DELETE(Path(Seg("users" :: name :: "bookmarks" :: uri))) => try {
      val uriString = uri mkString "/"
      logger.debug("DELETE /users/%s/bookmarks/%s".format(name, uriString))
      val Some(user) = userRepository findByName name
      val BasicAuth(u, p) = req
      val true = verify(u, p, user)
      val Some(_) = user.bookmarks.remove(uriString)
      NoContent
    } catch { case _: MatchError => NotFound }

Using for-comprehensions

A similar happy-path style with similar conciseness but without exceptions can be achieved using for-comprehensions with the Option monad. 8 LOC plus 4 LOC abstracted into the custom extractor.

    case req @ DELETE(Path(Seg("users" :: name :: "bookmarks" :: uri))) => {
      val uriString = uri mkString "/"
      logger.debug("DELETE /users/%s/bookmarks/%s" format (name, uriString))
      (for {
        MatchingBasicAuth(_) <- Some(req)
        _ <- repository.removeBookmark(name, uriString)
      } yield NoContent) getOrElse NotFound  
    }

Discussion

  • The nested style is ugly because it is verbose and includes multiple branches with the same result. The happy-path style is much more concise and easy to read.
  • The happy-path style “misuses” exceptions (in this case, MatchError) for non-erroneous control flow. This needs to be weighed against the conciseness and readability gains.
  • Why not an assertion instead of using true as a pattern? Because this is a functional concern, whereas assertions are a testing concern that might be disabled in production use.
  • The for-comprehension style using the Option monad is the most clean and concise. It also integrates beautifully with custom extractors.
  • Other thoughts?

103 notes  /  

By: laufer
Page 1 of 1