SIP-NN - Converters among optional Functions, PartialFunctions and extractor objects

Language

By: Yang Bo

History

Date Version
Aug 20th 2018 Initial Draft

Motivation

There are three types in Scala to represent a function that accept some of the parameters:

  1. optional functions: A => Option[B]
  2. extracter objects: { def unapply(a: A): Option[B] } and { def unapplySeq(a: A): Option[Seq[B]] }
  3. partial fucntions: PartialFunction[A, B]

Optional functions and partial functions can be converted to each other via PartialFunction.lift and Function.unlift. However, there is no simple approach to convert a partial function to an extractor object. As a result, partial functions are not composable. You cannot create a partial function then use it as a pattern in another partial function.

This proposal makes PartialFunction be an extractor, and provides an unlift method to convert optional functions to PartialFunctions.

Motivating Examples

// Define a PartialFunction
val pf: PartialFunction[Int, String] = {
  case 1 => "matched by a PartialFunction"
}

// Define an optional function
val of: Int => Option[String] = { i =>
  if (i == 2) {
    Some("matched by an optional function")
  } else {
    None
  }
}

util.Random.nextInt(4) match {
  case pf(m) => // A PartialFunction itself is a pattern
    println(m)
  case of.unlift(m) => // Convert an optional function to a pattern
    println(m)
  case _ =>
    println("Not matched")
}

In addition, elementWise can be used to create an object with a unapplySeq method, which extracts each element of a sequence data.

val firstChar: String => Option[Char] = _.headOption

Seq("foo", "bar", "baz") match {
  case firstChar.unlift.elementWise(c0, c1, c2) =>
    println(s"$c0, $c1, $c2") // Output: f, b, b
}

Cheat sheet

This proposal allows converting among optional Functions, PartialFunctions and extractor objects as shown in the following table.

How to convert ... to a partial function to an optional function to an extractor
from a partial function partialFunction partialFunction.lift partialFunction
from an optional function optionalFunction.unlift or Function.unlift(optionalFunction) optionalFunction optionalFunction.unlift
from an extractor { case extractor(x) => x } extractor.unapply _ extractor

Note that optionalFunction.unlift is preferred to Function.unlift(optionalFunction) when creating extractors, because only nullary methods are allowed in case expressions.

Implementation

The idea was originally implemented in a library: Extractor.scala, which has been used in Binding.scala and sbt-api-mappings.

The new implementation aims to become part of core library. The pull request can be found at #7111.

References

  1. Existing Implementation (Extractor.scala)
  2. Related Pull Request