Skip to content

Commit 5e547be

Browse files
authored
Merge pull request scala#7111 from Atry/extractor
Converters among optional Functions, PartialFunctions and extractor objects
2 parents b851a9f + ce891a4 commit 5e547be

File tree

4 files changed

+172
-1
lines changed

4 files changed

+172
-1
lines changed

src/build/genprod.scala

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ object genprod extends App {
5959

6060
def genprodString = " See scala.Function0 for timestamp."
6161
def moreMethods = ""
62+
def companionObject = ""
6263
def packageDef = "scala"
6364
def imports = ""
6465

@@ -150,6 +151,36 @@ object FunctionOne extends Function(1) {
150151
* @return a new function `f` such that `f(x) == g(apply(x))`
151152
*/
152153
@annotation.unspecialized def andThen[A](g: R => A): T1 => A = { x => g(apply(x)) }
154+
"""
155+
override def companionObject =
156+
"""
157+
object Function1 {
158+
159+
implicit final class UnliftOps[A, B] private[Function1](private val f: A => Option[B]) extends AnyVal {
160+
/** Converts an optional function to a partial function.
161+
*
162+
* @example Unlike [[Function.unlift]], this [[UnliftOps.unlift]] method can be used in extractors.
163+
* {{{
164+
* val of: Int => Option[String] = { i =>
165+
* if (i == 2) {
166+
* Some("matched by an optional function")
167+
* } else {
168+
* None
169+
* }
170+
* }
171+
*
172+
* util.Random.nextInt(4) match {
173+
* case of.unlift(m) => // Convert an optional function to a pattern
174+
* println(m)
175+
* case _ =>
176+
* println("Not matched")
177+
* }
178+
* }}}
179+
*/
180+
def unlift: PartialFunction[A, B] = Function.unlift(f)
181+
}
182+
183+
}
153184
"""
154185
}
155186

@@ -192,7 +223,7 @@ class Function(val i: Int) extends Group("Function") with Arity {
192223
def toStr() = "\"" + ("<function%d>" format i) + "\""
193224
def apply() = {
194225
<file name={fileName}>{header}
195-
226+
{companionObject}
196227
/** A function of {i} parameter{s}.
197228
*{descriptiveComment}
198229
*/

src/library/scala/Function1.scala

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,34 @@
1515
package scala
1616

1717

18+
object Function1 {
19+
20+
implicit final class UnliftOps[A, B] private[Function1](private val f: A => Option[B]) extends AnyVal {
21+
/** Converts an optional function to a partial function.
22+
*
23+
* @example Unlike [[Function.unlift]], this [[UnliftOps.unlift]] method can be used in extractors.
24+
* {{{
25+
* val of: Int => Option[String] = { i =>
26+
* if (i == 2) {
27+
* Some("matched by an optional function")
28+
* } else {
29+
* None
30+
* }
31+
* }
32+
*
33+
* util.Random.nextInt(4) match {
34+
* case of.unlift(m) => // Convert an optional function to a pattern
35+
* println(m)
36+
* case _ =>
37+
* println("Not matched")
38+
* }
39+
* }}}
40+
*/
41+
def unlift: PartialFunction[A, B] = Function.unlift(f)
42+
}
43+
44+
}
45+
1846
/** A function of 1 parameter.
1947
*
2048
* In the following example, the definition of succ is a

src/library/scala/PartialFunction.scala

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,37 @@ package scala
5252
* val numbers = sample map (isEven orElse isOdd)
5353
* }}}
5454
*
55+
* @note Optional [[Function]]s, [[PartialFunction]]s and extractor objects
56+
* can be converted to each other as shown in the following table.
57+
*
58+
* | How to convert ... | to a [[PartialFunction]] | to an optional [[Function]] | to an extractor |
59+
* | :---: | --- | --- | --- |
60+
* | from a [[PartialFunction]] | [[Predef.identity]] | [[lift]] | [[Predef.identity]] |
61+
* | from optional [[Function]] | [[Function.UnliftOps#unlift]] or [[Function.unlift]] | [[Predef.identity]] | [[Function.UnliftOps#unlift]] |
62+
* | from an extractor | `{ case extractor(x) => x }` | `extractor.unapply _` | [[Predef.identity]] |
5563
*
5664
* @author Martin Odersky, Pavel Pavlov, Adriaan Moors
5765
* @since 1.0
5866
*/
5967
trait PartialFunction[-A, +B] extends (A => B) { self =>
6068
import PartialFunction._
6169

70+
/** Tries to extract a `B` from an `A` in a pattern matching expression. */
71+
def unapply(a: A): Option[B] = lift(a)
72+
73+
/** Returns an extractor object with a `unapplySeq` method, which extracts each element of a sequence data.
74+
*
75+
* @example {{{
76+
* val firstChar: String => Option[Char] = _.headOption
77+
*
78+
* Seq("foo", "bar", "baz") match {
79+
* case firstChar.unlift.elementWise(c0, c1, c2) =>
80+
* println(s"$c0, $c1, $c2") // Output: f, b, b
81+
* }
82+
* }}}
83+
*/
84+
def elementWise = new ElementWiseExtractor(this)
85+
6286
/** Checks if a value is contained in the function's domain.
6387
*
6488
* @param x the value to test
@@ -202,6 +226,16 @@ trait PartialFunction[-A, +B] extends (A => B) { self =>
202226
* @since 2.8
203227
*/
204228
object PartialFunction {
229+
230+
final class ElementWiseExtractor[-A, +B] private[PartialFunction] (private val pf: PartialFunction[A, B]) extends AnyVal {
231+
def unapplySeq(seq: Seq[A]): Option[Seq[B]] = {
232+
Some(seq.map {
233+
case pf(b) => b
234+
case _ => return None
235+
})
236+
}
237+
}
238+
205239
/** Composite function produced by `PartialFunction#orElse` method
206240
*/
207241
private class OrElse[-A, +B] (f1: PartialFunction[A, B], f2: PartialFunction[A, B])

test/junit/scala/ExtractorTest.scala

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package scala
2+
3+
import org.junit.Assert._
4+
import org.junit.Test
5+
6+
import scala.tools.testkit.RunTesting
7+
8+
class ExtractorTest extends RunTesting {
9+
val of: Int => Option[String] = { x =>
10+
if (x == 0) {
11+
Some("zero")
12+
} else if (x == 1) {
13+
Some("one")
14+
} else {
15+
None
16+
}
17+
}
18+
19+
@Test def testOptionalFunctionExtractor(): Unit = {
20+
1 match {
21+
case of.unlift(m) =>
22+
assertEquals(m, "one")
23+
}
24+
}
25+
26+
@Test(expected = classOf[MatchError]) def testOptionalFunctionExtractorMatchError(): Unit = {
27+
2 match {
28+
case of.unlift(m) =>
29+
}
30+
}
31+
32+
@Test def testOptionalFunctionExtractorSeq(): Unit = {
33+
Seq(0, 1) match {
34+
case of.unlift.elementWise(m0, m1) =>
35+
assertEquals(m0, "zero")
36+
assertEquals(m1, "one")
37+
}
38+
}
39+
40+
@Test(expected = classOf[MatchError]) def testOptionalFunctionExtractorSeqMatchError(): Unit = {
41+
Seq(1, 2) match {
42+
case of.unlift.elementWise(m0, m1) =>
43+
}
44+
}
45+
46+
val pf: PartialFunction[Int, String] = {
47+
case 0 => "zero"
48+
case 1 => "one"
49+
}
50+
51+
@Test def testPartialFunctionExtractor(): Unit = {
52+
1 match {
53+
case pf(m) =>
54+
assertEquals(m, "one")
55+
}
56+
}
57+
58+
@Test(expected = classOf[MatchError]) def testPartialFunctionExtractorMatchError(): Unit = {
59+
2 match {
60+
case pf(m) =>
61+
}
62+
}
63+
64+
@Test def testPartialFunctionExtractorSeq(): Unit = {
65+
Seq(0, 1) match {
66+
case pf.elementWise(m0, m1) =>
67+
assertEquals(m0, "zero")
68+
assertEquals(m1, "one")
69+
}
70+
}
71+
72+
@Test(expected = classOf[MatchError]) def testPartialFunctionExtractorSeqMatchError(): Unit = {
73+
Seq(1, 2) match {
74+
case pf.elementWise(m0, m1) =>
75+
}
76+
}
77+
78+
}

0 commit comments

Comments
 (0)