Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Explain this pattern matching code

Tags:

scala

This code is from Querying a Dataset with Scala's Pattern Matching:

object & { def unapply[A](a: A) = Some((a, a)) }

"Julie" match {
  case Brothers(_) & Sisters(_) => "Julie has both brother(s) and sister(s)"
  case Siblings(_) => "Julie's siblings are all the same sex"
  case _ => "Julie has no siblings"
}

// => "Julie has both brother(s) and sister(s)"

How does & actually work? I don't see a Boolean test anywhere for the conjunction. How does this Scala magic work?

like image 277
Jim Avatar asked Oct 24 '10 12:10

Jim


People also ask

What is pattern matching in code?

Pattern matching is a technique where you test an expression to determine if it has certain characteristics. C# pattern matching provides more concise syntax for testing expressions and taking action when an expression matches.

What is pattern matching give an example?

For example, x* matches any number of x characters, [0-9]* matches any number of digits, and . * matches any number of anything. A regular expression pattern match succeeds if the pattern matches anywhere in the value being tested.

How does pattern matching work?

Pattern Matching works by "reading" through text strings to match patterns that are defined using Pattern Matching Expressions, also known as Regular Expressions. Pattern Matching can be used in Identification as well as in Pre-Classification Processing, Page Processing, or Storage Processing.

What is pattern matching explain different methods used in pattern matching?

Pattern matching is the process of checking whether a specific sequence of characters/tokens/data exists among the given data. Regular programming languages make use of regular expressions (regex) for pattern matching.


2 Answers

Here's how unapply works in general:

When you do

obj match {case Pattern(foo, bar) => ... }

Pattern.unapply(obj) is called. This can either return None in which case the pattern match is a failure, or Some(x,y) in which case foo and bar are bound to x and y.

If instead of Pattern(foo, bar) you did Pattern(OtherPattern, YetAnotherPatter) then x would be matched against the pattern OtherPattern and y would be matched against YetAnotherPattern. If all of those pattern matches are successful, the body of the match executes, otherwise the next pattern is tried.

when the name of a pattern is not alphanumeric, but a symbol (like &), it is used infix, i.e. you write foo & bar instead of &(foo, bar).


So here & is a pattern that always returns Some(a,a) no matter what a is. So & always matches and binds the matched object to its two operands. In code that means that

obj match {case x & y => ...}

will always match and both x and y will have the same value as obj.

In the example above this is used to apply two different patterns to the same object.

I.e. when you do

obj match { case SomePattern & SomeOtherPattern => ...}`

first the pattern & is applied. As I said, it always matches and binds obj to its LHS and its RHS. So then SomePattern is applied to &'s LHS (which is the same as obj) and SomeOtherPattern is applied to &'s RHS (which is also the same as obj).

So in effect, you just applied two patterns to the same object.

like image 103
sepp2k Avatar answered Sep 18 '22 15:09

sepp2k


Let's do this from the code. First, a small rewrite:

object & { def unapply[A](a: A) = Some(a, a) }

"Julie" match {
  // case Brothers(_) & Sisters(_) => "Julie has both brother(s) and sister(s)"
  case &(Brothers(_), Sisters(_)) => "Julie has both brother(s) and sister(s)"
  case Siblings(_) => "Julie's siblings are all the same sex"
  case _ => "Julie has no siblings"
}

The new rewrite means exactly the same thing. The comment line is using infix notation for extractors, and the second is using normal notation. They both translate to the same thing.

So, Scala will feed "Julie" to the extractor, repeatedly, until all unbound variables got assigned to Some thing. The first extractor is &, so we get this:

&.unapply("Julie") == Some(("Julie", "Julie"))

We got Some back, so we can proceed with the match. Now we have a tuple of two elements, and we have two extractors inside & as well, so we feed each element of the tuple to each extractor:

Brothers.unapply("Julie") == ?
Sisters.unapply("Julie") == ?

If both of these return Some thing, then the match is succesful. Just for fun, let's rewrite this code without pattern matching:

val pattern = "Julie"
val extractor1 = &.unapply(pattern)
if (extractor1.nonEmpty && extractor1.get.isInstanceOf[Tuple2]) {
  val extractor11 = Brothers.unapply(extractor1.get._1)
  val extractor12 = Sisters.unapply(extractor1.get._2)
  if (extractor11.nonEmpty && extractor12.nonEmpty) {
    "Julie has both brother(s) and sister(s)"
  } else {
    "Test Siblings and default case, but I'll skip it here to avoid repetition" 
  }
} else {
  val extractor2 = Siblings.unapply(pattern)
  if (extractor2.nonEmpty) {
    "Julie's siblings are all the same sex"
  } else {
    "Julie has no siblings"
}

Ugly looking code, even without optimizing to only get extractor12 if extractor11 isn't empty, and without the code repetition that should have gone where there's a comment. So I'll write it in yet another style:

val pattern = "Julie"
& unapply pattern  filter (_.isInstanceOf[Tuple2]) flatMap { pattern1 =>
  Brothers unapply pattern1._1 flatMap { _ =>
    Sisters unapply pattern1._2 flatMap { _ =>
      "Julie has both brother(s) and sister(s)"
    }
  }
} getOrElse {
  Siblings unapply pattern map { _ =>
    "Julie's siblings are all the same sex"
  } getOrElse {
    "Julie has no siblings"
  }
}

The pattern of flatMap/map at the beginning suggests yet another way of writing this:

val pattern = "Julie"
(
  for {
    pattern1 <- & unapply pattern
    if pattern1.isInstanceOf[Tuple2]
    _ <- Brothers unapply pattern1._1
    _ <- Sisters unapply pattern1._2
  } yield "Julie has both brother(s) and sister(s)
) getOrElse (
 for {
   _ <- Siblings unapply pattern
 } yield "Julie's siblings are all the same sex"
) getOrElse (
  "julie has no siblings"
)

You should be able to run all this code and see the results for yourself.

like image 41
Daniel C. Sobral Avatar answered Sep 21 '22 15:09

Daniel C. Sobral