diff --git a/_sips/sips/2018-08-20-converters-among-optional-functions-partialfunctions-and-extractor-objects.md b/_sips/sips/2018-08-20-converters-among-optional-functions-partialfunctions-and-extractor-objects.md new file mode 100644 index 0000000000..0e2b8e9ca5 --- /dev/null +++ b/_sips/sips/2018-08-20-converters-among-optional-functions-partialfunctions-and-extractor-objects.md @@ -0,0 +1,147 @@ +--- +layout: sips +discourse: true +title: SIP-NN - Converters among optional Functions, PartialFunctions and extractor objects +--- + +**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 `PartialFunction`s. + +## Motivating Examples + +{% highlight scala %} +// 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") +} +{% endhighlight %} + +In addition, `elementWise` can be used to create an object with a `unapplySeq` method, which extracts each element of a sequence data. + +{% highlight scala %} +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 +} +{% endhighlight %} + +## 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](https://github.com/ThoughtWorksInc/Extractor.scala), which has been used in [Binding.scala](https://github.com/ThoughtWorksInc/Binding.scala/blob/10.0.x/XmlExtractor/src/main/scala/com/thoughtworks/binding/XmlExtractor.scala#L63) and [sbt-api-mappings](https://github.com/ThoughtWorksInc/sbt-api-mappings/blob/f4e1353/src/main/scala/com/thoughtworks/sbtApiMappings/ApiMappings.scala#L48). + +The new implementation aims to become part of core library. The pull request can be found at [#7111][2]. + +## References + +1. [Existing Implementation (Extractor.scala)][1] +2. [Related Pull Request][2] + +[1]: https://github.com/ThoughtWorksInc/Extractor.scala "Extractor.scala" +[2]: https://github.com/scala/scala/pull/7111 "#7111" \ No newline at end of file