-
Notifications
You must be signed in to change notification settings - Fork 1k
Rewrote extractors section of tour #761
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
@julienrf @SethTisue any comments? |
The `apply` method is not necessary for pattern matching. It is only used to mimick a constructor. `val x = Twice(21)` expands to `val x = Twice.apply(21)`. | ||
def unapply(customerID: String): Option[String] = { | ||
val name = customerID.split("--")(0) | ||
if (!name.isEmpty) Some(name) else None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could use if (name.nonEmpty) …
.
On the line above, you could also use customerID.split("--").head
, or even .headOption
, but split
always returns at least one element, so I think that’s fine…
val CustomerID(name) = customer1ID | ||
println(name) // Sukyoung | ||
``` | ||
The `apply` method creates a `CustomerID` string from a `name`. The `unapply` does the inverse to get the `name` back. Although `CustomerID` doesn't take any parameters, when we call `CustomerID("Sukyoung")`, this is shorthand syntax for calling `CustomerID.apply("Sukyoung")`. When we call `val CustomerID(name) = customer1ID`, this is shorthand for `val name = CustomerID.unapply(name)`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not exact: val CustomerID(name) = customer1ID
is a shorthand for val name = CustomerID.unapply(name).get
(note the trailing .get
call that is missing in your description).
Also, I think it is worth reminding that patterns can also be used in pattern matching (not just in val definitions).
Finally, the unapply
method don’t have to return an Option
(but that information is relevant for advanced users only, I think), and you can also define an unapplySeq
method that extracts a variable number of information.
(it's unlikely I'll have any time to review any of the tour PRs until May, I'm afraid) |
50343c1
to
c3ea3ba
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
More generally:
Extractors more commonly appear as case
s in a PF or match
. The emphasis on val
extractions doesn't reflect common use, though it is useful as well.
At the very least, it is worth noting here that, in general, extractors disable exhaustiveness checking, because a new user who has seen exhaustiveness checking in action will likely intuit that it will work for these too, even though it cannot.
Finally, speaking of matches to val
, ascribing an irrefutable extractor with Some
instead of Option
has an interesting result:
scala> object RestString {
def unapply(s: String): Some[String] = Some(s drop 1)
}
defined object RestString
scala> "ooh" match {case RestString(q) => q; case _ => "nope"}
<console>:13: warning: unreachable code
"ooh" match {case RestString(q) => q; case _ => "nope"}
^
res1: String = oh
This rather positive effect implies that it may be worth recommending that irrefutable extractors, those explicitly designed for extraction with val
instead of might-fail patterns, ought to be ascribed in this way.
@@ -11,32 +11,41 @@ next-page: sequence-comprehensions | |||
previous-page: regular-expression-patterns | |||
--- | |||
|
|||
In Scala, patterns can be defined independently of case classes. To this end, a method named unapply is defined to yield a so-called extractor. An extractor can be thought of as a special method that reverses the effect of applying a particular object on some inputs. Its purpose is to 'extract' the inputs that were present before the 'apply' operation. For instance, the following code defines an extractor [object](singleton-objects.html) Twice. | |||
|
|||
An extractor object is an object with an `unapply` method. Whereas the a `apply` method is like a constructor which takes arguments and creates an object, the `unapply` takes an object and tries to give back the arguments. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/the a/a/
|
||
The pattern `case Twice(n)` will cause an invocation of `Twice.unapply`, which is used to match any even number; the return value of the `unapply` signals whether the argument has matched or not, and any sub-values that can be used for further matching. Here, the sub-value is `z/2` | ||
def unapply(customerID: String): Option[String] = { | ||
val name = customerID.split("--").head |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you account for CustomerID("a--b")
without overly complicating the example? (e.g. split only the last one)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@S11001001 I'm not sure what you mean, can you elaborate?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently:
scala> val CustomerID(x) = CustomerID("a--b")
x: String = a
|
||
The `apply` method is not necessary for pattern matching. It is only used to mimick a constructor. `val x = Twice(21)` expands to `val x = Twice.apply(21)`. | ||
val customer1ID = CustomerID("Sukyoung") // Sukyoung-23098234908 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be two hyphens.
val CustomerID(name) = customer1ID | ||
println(name) // prints Sukyoung | ||
``` | ||
The `apply` method creates a `CustomerID` string from a `name`. The `unapply` does the inverse to get the `name` back. When we call `CustomerID("Sukyoung")`, this is shorthand syntax for calling `CustomerID.apply("Sukyoung")`. When we call `val CustomerID(name) = customer1ID`, this is equivalent to `val name = CustomerID.unapply(customer1ID)`. This is used in pattern matching like so: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The second equivalence does not hold, because unapply
returns Option[String]
.
In fact, if we tried to unapply an invalid `CustomerID`, we would get a `scala.MatchError`: | ||
```tut:fail | ||
val CustomerID(name2) = "--asdfasdfasdf" | ||
name2.isEmpty |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line will fail in tut
because name2
is unbound, which probably obscures the point you're making (where only the first line's error is relevant).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The val
keyword doesn't bind it? I'm seeing a MatchError when I run it in a worksheet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@travissarles yeah I think you're right here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here is what tut emits. Note the second error.
This is equivalent to val name = CustomerID.unapply(customer2ID).get
. If there is no match, a scala.MatchError
is thrown:
scala> val CustomerID(name2) = "--asdfasdfasdf"
scala.MatchError: --asdfasdfasdf (of class java.lang.String)
... 198 elided
scala> name2.isEmpty
<console>:14: error: not found: value name2
name2.isEmpty
^
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah, I see. because tut is REPL based.
nbd either way I think, but I'd be fine with omitting the name2.isEmpty
line
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@S11001001 I took out the name2.isEmpty as it's not helpful
Interesting, didn't know this trick. Thanks for sharing. |
f5093a3
to
96c0032
Compare
```tut | ||
val customer2ID = CustomerID("Nico") | ||
val CustomerID(name) = customer2ID | ||
println(name) // prints Nico |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can probably do without this println
, as tut writes
scala> val customer2ID = CustomerID("Nico")
customer2ID: String = Nico--8217157908548994001
scala> val CustomerID(name) = customer2ID
name: String = Nico
scala> println(name) // prints Nico
Nico
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We aren't actually using the output of tut. We're just using it to verify that code snippets compile (or don't). Actually using the output is a possible future improvement.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@SethTisue Ah, I see.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to use Scastie in the future so people can see the output and play around with the examples.
96c0032
to
80b5e9c
Compare
@julienrf any other comments? |
No description provided.