3

There is an aspect of the pattern matching I don't understand.

In the documentation of the pattern matching they show an example such as :

https://docs.scala-lang.org/tour/pattern-matching.html

abstract class Notification

case class Email(sender: String, title: String, body: String) extends Notification

case class SMS(caller: String, message: String) extends Notification

case class VoiceRecording(contactName: String, link: String) extends Notification

def showNotification(notification: Notification): String = {
  notification match {
    case Email(sender, title, _) =>
      s"You got an email from $sender with title: $title"
    case SMS(number, message) =>
      s"You got an SMS from $number! Message: $message"
    case VoiceRecording(name, link) =>
      s"You received a Voice Recording from $name! Click the link to hear it: $link"
  }
}
val someSms = SMS("12345", "Are you there?")
val someVoiceRecording = VoiceRecording("Tom", "voicerecording.org/id/123")

Which could be somewhat recoded in java such as :

Notification notification = /* Init a notification instance */

if(notification instanceof Email) {
    Email currentEmail = (Email) notification;
    currentEmail.doSomething();
} else if (notification instanceof SMS) {
    SMS currentSMS = (SMS) notification;
    currentSMS.doSomething();
} else if {
    /* ... */
}

Pattern matching seems to be very positively seen but on the opposite the java equivalent is seen as a "code smell" or a bad pattern.

From my understand they are doing the same thing and maybe technically as well, it's just hidden for the scala pattern matching.

Yet such a double standard wouldn't stay unseen so I guess there is something wrong with my understanding.

phatfingers
  • 9,770
  • 3
  • 30
  • 44
Omegaspard
  • 1,828
  • 2
  • 24
  • 52

2 Answers2

11

Under the hood, Scala pattern matching often boils down to code that's exactly like the if (notification instanceof Email) { ... } else if (notification instanceof SMS) Java code.

The particular example you give, of an abstract class Notification which isn't sealed is one where the Scala code is no better (except perhaps expressing overall intent more clearly) than the if/instanceof tree.

This is because the main benefit of pattern matching is the possibility of exhaustivity checking. With the if/instanceof approach and the example of pattern matching you present, you aren't going to be alerted that you haven't handled every case (e.g. you left off the VoiceRecording case).

By making Notification sealed (e.g. sealed abstract class Notification), the Scala compiler will ensure that no Scala code in other files (technically, compilation units, which are for all intents and purposes files) can extend Notification; since it now knows all the possible Notifications, it can then raise a compiler error if you miss a case. There's no reliable way to do this in the if/instanceof case because that's a lower level of abstraction.

Levi Ramsey
  • 18,884
  • 1
  • 16
  • 30
3

The "smell" has been historic. Object oriented design suggests you should be able to let the type hierarchy dispatch your method call to the appropriate subtype, rather than checking for actual instances at the call site. There had been some bad designs related to overuse of inheritance, which had lead to such instanceof usage. The instanceof usage wasn't the bad thing, but it hinted at the type hierarchy possibly being bad.

On the other hand, Java 17 now has Scala's pattern matching, too (as a preview feature, soon to be finalised), so you can't really say that using instanceof is a bad thing in Java.

Lukas Eder
  • 211,314
  • 129
  • 689
  • 1,509
  • Do you have a reference to the "There had been some bad designs related to overuse of inheritance" related to the example I state in my original post ? I fail to see where the interface method call would be worse than a the use of "instance of". – Omegaspard Feb 07 '22 at 12:45
  • 1
    @RobertReynolds: Well, if you wanted to implement the Java version in an object oriented way, your `doSomething()` method would instead be called e.g. `format(): String`, and it would be declared on `Notification` rather than only on the subtypes. You'd "encapsulate" the actual formatting and hide it from the call site (more OO-ish design), rather than implementing it entirely at the call site (more FP-ish design). So, in classic OO design, this would be the criticism, and your `instanceof` usage would hint at your class hierarchy lacking the appropriate abstraction over `format()` methods. – Lukas Eder Feb 07 '22 at 18:13
  • 1
    @RobertReynolds: But as I wanted to point out, that's just a programming style (the OO one). FP is another one. Both are viable, and the latter is getting more popular also in the Java ecosystem. So, I wouldn't say that it's really a "smell" or "bad pattern" – Lukas Eder Feb 07 '22 at 18:13
  • I think I don't see any benefit in the FP one here. With OO programing using an interface would transform a 4 line pattern matching instruction to 1 line of code since we don't need to do any check, we just call the method that any Notification object should implement no ? So why would we do the patern matching in this case in scala ? Maybe my question is too specific and it doesn't have any anser, but it's because of stuff like this that I can't grasp FP concepts. – Omegaspard Feb 08 '22 at 10:43
  • 1
    @RobertReynolds: It's "simple": Is this template something that needs to be reused dozens of times, and thus worthy of putting on the API of this type hierarchy? Then do so. Is it something that you do only exactly once, something that doesn't really have anything to do with the type hierarchy? Then keep it as it is. This decision doesn't strictly relate to OO vs FP. In classic OO, the pattern matching approach could have been implemented using the visitor pattern... – Lukas Eder Feb 08 '22 at 12:05
  • It's clearer, ty so much. – Omegaspard Feb 08 '22 at 15:35