Skip to content

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

Merged
merged 1 commit into from
May 8, 2017

Conversation

travissarles
Copy link
Contributor

No description provided.

@travissarles
Copy link
Contributor Author

@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
Copy link
Contributor

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)`.
Copy link
Contributor

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.

@SethTisue
Copy link
Member

@SethTisue any comments?

(it's unlikely I'll have any time to review any of the tour PRs until May, I'm afraid)

Copy link
Contributor

@S11001001 S11001001 left a 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 cases 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.
Copy link
Contributor

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
Copy link
Contributor

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)

Copy link
Contributor Author

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?

Copy link
Contributor

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
Copy link
Contributor

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:
Copy link
Contributor

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
Copy link
Contributor

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).

Copy link
Contributor Author

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.

Copy link
Member

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

Copy link
Contributor

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
       ^

Copy link
Member

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

Copy link
Contributor Author

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

@jvican
Copy link
Member

jvican commented Apr 17, 2017

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.

Interesting, didn't know this trick. Thanks for sharing.

@travissarles travissarles force-pushed the extractor-objects branch 2 times, most recently from f5093a3 to 96c0032 Compare April 28, 2017 15:00
```tut
val customer2ID = CustomerID("Nico")
val CustomerID(name) = customer2ID
println(name) // prints Nico
Copy link
Contributor

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

Copy link
Member

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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@SethTisue Ah, I see.

Copy link
Contributor Author

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.

@travissarles
Copy link
Contributor Author

@julienrf any other comments?

@travissarles travissarles merged commit 81d6978 into scala:master May 8, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants