|
| 1 | +--- |
| 2 | +layout: sips |
| 3 | +discourse: true |
| 4 | +title: SIP-NN - Converters among optional Functions, PartialFunctions and extractor objects |
| 5 | +--- |
| 6 | + |
| 7 | +**By: Yang Bo** |
| 8 | + |
| 9 | + |
| 10 | +## History |
| 11 | + |
| 12 | +| Date | Version | |
| 13 | +|---------------|---------------| |
| 14 | +| Aug 20th 2018 | Initial Draft | |
| 15 | + |
| 16 | +## Motivation |
| 17 | + |
| 18 | +There are three types in Scala to represent a function that accept some of the parameters: |
| 19 | + |
| 20 | +1. optional functions: `A => Option[B]` |
| 21 | +2. extracter objects: `{ def unapply(a: A): Option[B] }` and `{ def unapplySeq(a: A): Option[Seq[B]] }` |
| 22 | +3. partial fucntions: `PartialFunction[A, B]` |
| 23 | + |
| 24 | +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. |
| 25 | + |
| 26 | +This proposal makes `PartialFunction` be an extractor, and provides an `unlift` method to convert optional functions to `PartialFunction`s. |
| 27 | + |
| 28 | +## Motivating Examples |
| 29 | + |
| 30 | +{% highlight scala %} |
| 31 | +// Define a PartialFunction |
| 32 | +val pf: PartialFunction[Int, String] = { |
| 33 | + case 1 => "matched by a PartialFunction" |
| 34 | +} |
| 35 | + |
| 36 | +// Define an optional function |
| 37 | +val of: Int => Option[String] = { i => |
| 38 | + if (i == 2) { |
| 39 | + Some("matched by an optional function") |
| 40 | + } else { |
| 41 | + None |
| 42 | + } |
| 43 | +} |
| 44 | + |
| 45 | +util.Random.nextInt(4) match { |
| 46 | + case pf(m) => // A PartialFunction itself is a pattern |
| 47 | + println(m) |
| 48 | + case of.unlift(m) => // Convert an optional function to a pattern |
| 49 | + println(m) |
| 50 | + case _ => |
| 51 | + println("Not matched") |
| 52 | +} |
| 53 | +{% endhighlight %} |
| 54 | + |
| 55 | +In addition, `elementWise` can be used to create an object with a `unapplySeq` method, which extracts each element of a sequence data. |
| 56 | + |
| 57 | +{% highlight scala %} |
| 58 | +val firstChar: String => Option[Char] = _.headOption |
| 59 | + |
| 60 | +Seq("foo", "bar", "baz") match { |
| 61 | + case firstChar.unlift.elementWise(c0, c1, c2) => |
| 62 | + println(s"$c0, $c1, $c2") // Output: f, b, b |
| 63 | +} |
| 64 | +{% endhighlight %} |
| 65 | + |
| 66 | +## Implementation |
| 67 | + |
| 68 | +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). |
| 69 | + |
| 70 | +The new implementation aims to become part of core library. The pull request can be found at [#7111][2]. |
| 71 | + |
| 72 | +## Drawbacks |
| 73 | + |
| 74 | +Why should we *not* do this. Be honest, these questions will come out during the |
| 75 | +process anyway so it's better to get them out up front. |
| 76 | + |
| 77 | +## References |
| 78 | + |
| 79 | +1. [Existing Implementation (Extractor.scala)][1] |
| 80 | +2. [Related Pull Request][2] |
| 81 | + |
| 82 | +[1]: https://github.com/ThoughtWorksInc/Extractor.scala "Extractor.scala" |
| 83 | +[2]: https://github.com/scala/scala/pull/7111 "#7111" |
0 commit comments