|
| 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 provide an `extract` method to convert all these representation to extractor objects. |
| 27 | + |
| 28 | +## Motivating Examples |
| 29 | + |
| 30 | +{% highlight scala %} |
| 31 | +// Define a PartialFunction |
| 32 | +val pf: PartialFunction[Int, String] = { |
| 33 | + case 1 => "matched by PartialFunction" |
| 34 | +} |
| 35 | + |
| 36 | +// Define an optional function |
| 37 | +val f: Int => Option[String] = { i => |
| 38 | + if (i == 2) { |
| 39 | + Some("matched by optional function") |
| 40 | + } else { |
| 41 | + None |
| 42 | + } |
| 43 | +} |
| 44 | + |
| 45 | +// Convert an optional function to a PartialFunction |
| 46 | +val pf2: PartialFunction[Int, String] = Function.unlift(f) |
| 47 | + |
| 48 | +util.Random.nextInt(4) match { |
| 49 | + case pf.extract(m) => // Convert a PartialFunction to a pattern |
| 50 | + println(m) |
| 51 | + case f.extract(m) => // Convert an optional function to a pattern |
| 52 | + println(m) |
| 53 | + case pf2.extract(m) => // Convert a PartialFunction to a pattern |
| 54 | + throw new AssertionError("This case should never occur because it has the same condition as `f.extract`.") |
| 55 | + case _ => |
| 56 | + println("Not matched") |
| 57 | +} |
| 58 | +{% endhighlight %} |
| 59 | + |
| 60 | +Alternatively, `extract.seq` can be used to create an object with a `unapplySeq` method, which extracts each element of a sequence data. |
| 61 | + |
| 62 | +{% highlight scala %} |
| 63 | +val csvRow: String => Option[Seq[String]] = { line => |
| 64 | + Some(line.split(',')) |
| 65 | +} |
| 66 | + |
| 67 | +"foo,bar,baz" match { |
| 68 | + case csvRow.extract.seq(cell0, cell1, cell2) => |
| 69 | + println(s"cell1=$cell1") // Output: cell1=bar |
| 70 | +} |
| 71 | +{% endhighlight %} |
| 72 | + |
| 73 | +## Implementation |
| 74 | + |
| 75 | +The `extract` method has been implemented in a library: [Extractor.scala](https://github.com/ThoughtWorksInc/Extractor.scala), which has been used in Binding.scala in order to [extract patterns of XML literals](https://github.com/ThoughtWorksInc/Binding.scala/blob/10.0.x/XmlExtractor/src/main/scala/com/thoughtworks/binding/XmlExtractor.scala#L63). |
| 76 | + |
| 77 | +## Drawbacks |
| 78 | + |
| 79 | +Why should we *not* do this. Be honest, these questions will come out during the |
| 80 | +process anyway so it's better to get them out up front. |
| 81 | + |
| 82 | +## References |
| 83 | + |
| 84 | +1. [Existing Implementation (Extractor.scala)][1] |
| 85 | + |
| 86 | +[1]: https://github.com/ThoughtWorksInc/Extractor.scala "Extractor.scala" |
0 commit comments