Eugene Krotov , Scala / Java Developer
When you start developing your first Scala projects, you may sometimes miss out to address one common problem with pattern matching, and that is error handling. Basically, you may pass a type to a method used for matching and that type doesn't really match any pattern, which will lead to a runtime error.
Say, you created an abstract class
Filter, a few case classes that inherit
Filter and provide a specific filter
type, and a method
filterValues() that accepts the filter parameter and uses pattern matching to verify some input.
Here's what the described implementation might look like:
1 2 3 4 5 6 7 8 9 10
Now your colleague, also a Scala developer, creates their own case class that extends
Filter to verify some value. But
they may forget to add their class as a case parameter in all places where it's used. Eventually, the Scala
application you're both working on may fail with the
MatchError runtime exception. Needless to say that runtime errors
are really bad.
You should always pay attention to this kind of errors when using pattern matching because you can never know when your application may break.
In this article, we have a look at a few ways to handle the
MatchError runtime exception in Scala:
- Adding a default case
_to catch all unmatched options
- Using the keyword
sealedwith the base class or trait for matching options (although this is a workaround not a problem solver)
- Changing the compiler options to stop compilation whenever there are warnings produced thanks to
Step by step, we review the listed ways to address or mitigate the problem.
Using a default case with pattern matching
The first implementation of
filterValues() missed possible variants of the parameter type, which is why the compiler
won't be able to notice an error. You also won't be able to properly process input parameters.
When using pattern matching, however, you can match an unexpected parameter with an "all-type" option:
1 2 3 4 5
With the implementation above, you can apply a default filter
_ (the last case), an irrefutable pattern, which is
similar to the default case in Java switch statements.
_ matches any kind of data you throw into the method.
To handle the mismatch with
case _, you can throw an error, log out a message to the console, or process a case any
other way according to the needs of your application.
This solution isn't perfect, though, as you probably want to keep the method implementation without any extra cases.
sealed to get warnings when a pattern doesn't match
Scala provides a way to generate warnings whenever some pattern doesn't match. That is, you can use the keyword
when defining your abstract class (or trait) for pattern matching. Marking a class with
sealed tells that the subtypes
must be declared in the same file to ensure that all of them are known.
This is how the updated code looks:
1 2 3 4 5 6 7 8 9 10
If you try to run your code with a
sealed class, the Scala compiler will warn you about the issue:
1 2 3 4 5 6
However, just placing the keyword
sealed before a class doesn't actually solve the problem. You do get cleaner code
compared to the use of the irrefutable pattern, though. And to find the problematic use of pattern matching, you can
simply inspect the console output to find warnings.
In order not to miss
MatchError, you need to configure the Scala compiler to throw errors whenever this exception
So we want the compiler stop the application compilation and say something like: "Look, man, you have a method that
calls your filter but these inputs have failed. Check them, please." The compiler setting you're looking for is called
-Xfatal-warnings, which fails the compilation if there are any warnings:
1 2 3
Once you set this options and use
sealed with your abstract classes for pattern matching, the Scala compiler will
always stop building your project whenever a warning is produced, which is great.
Removing annoying warnings
Our talk about the Scala best practices for pattern matching would be incomplete without a solution to ignore warnings
when we don't need them. Often, you can understand from the context that some unexpected type will never be passed as a
parameter to your method, and you're sure that
MatchError won't be thrown. But if you're using
sealed, warnings will
still be shown in the console.
Have a look a this example:
1 2 3 4 5
apply() that can match a parameter
move to the defined cases for, say,
Car objects, may not use
all move patterns by design, as shown in the example below, thus producing annoying warnings. To remove them, you can
use the annotation
@unchecked in the selector:
1 2 3 4
This last example ensures that deep pattern matching won't be executed. Remember to use it with care, as it may lead to downsides described in the beginning of the post.
That’s all you need to know about handling the pattern matching
MatchError runtime exception in your Scala