One of Swift's great features is the extension of pattern matching. A pattern is a rule value used for matching, such as the case of a switch statement, the catch clause of a do statement, and the conditions of if, while, guard, and for-in statements. For example, suppose you want to determine whether an integer is greater than, less than, or equal to zero. You could use an if-else if-else statement, although it's not very pretty:
Using a switch statement would be much better. My ideal code would be this:
But pattern matching doesn't support inequalities by default. Let's see if we can change that. To make things clearer, I'll ignore the >0 case and replace it with greaterThan(0), and I'll define this operator later. Extended pattern matching Swift's pattern matching is based on the ~= operator, which matches if the ~= value of the expression returns true. The standard library comes with four ~= operator overloads: one for Equatable, one for Optional, one for Range, and one for Interval. None of these meet our needs, although Range and Interval are very close, and you can read this article about them. So we have to implement our own ~=. The prototype of this method is:
We know that this method must return a Bool, and that's exactly what we need, we need to know if the value matches the pattern. The next thing to ask ourselves is: what is the type of the parameter? For the value, we can use Int, which is what we needed in the previous example. But let's generalize it to accept any type. In our case, the pattern is like greaterThan(001.png) or lessThan(001.png). More generally, the pattern should be a method, a method that takes a value as an argument and returns true or false. The value is of type T, so the pattern should be of type T -> Bool:
Now we need to define the methods greaterThan and lessThan to create the pattern. Be careful not to confuse the 0 in the pattern greaterThan(0) with the value we want to match. The parameter to greaterThan is part of the pattern, which will be used in the second step. For example, greaterThan(0) ~= x is the same as greaterThan(0)(x). We know that the method greaterThan(0) must return a method that takes a value and returns Bool. So greaterThan must be a method that takes another value and returns the previous method. We restrict the parameter to Comparable in order to use Swift's > and < operators in the implementation:
This method accepts one parameter, calls a method that accepts more than one parameter and returns it. Methods like this are called Curried functions. (Some instance methods in Swift are Curried functions.) Swift provides a special syntax for Curried functions, just as their name suggests. Using this syntax, our method becomes like this:
So we have the first version of the switch statement:
Very nice, but look at the default, this solution doesn't give the compiler any hint to do sanity checking, so we have to provide a default. If you are sure that the pattern covers every possible value, it is a good idea to call fatalError() in the default, which indicates that this code will never be executed. Custom Operators Think back to the initial idea, and the pseudocode. Ideally, we would like to replace greaterThan(0) and lessThan(0) with >0 and <0. Custom operators are controversial because other readers are often unfamiliar with them and they reduce readability. Back to our example, something like greaterThan(0) is perfectly readable, so it could be argued that a custom operator is not needed. But at the same time, everyone knows what >0 means. So let's try it, but as we'll see, it's not going to be pretty. Our custom operators are unary - they have only one operand. Also, they are prefix operators (as opposed to postfix, where the operator comes after the operand). There can't be a space between the unary operator and the operand, because Swift uses spaces to distinguish between unary and binary operators. Also, < is not allowed as a prefix operator, so we have to use something else instead. (> is allowed as a prefix, but not as a postfix). I suggest we use ~> and ~<. While ~> is not ideal because it only looks like an arrow, the tilde hints at the pattern matching operator ~=. Other operators I can think of (like >> and <<) are confusing. Update 25 Sep: I learned from Nate Cook that the operator ~> already exists in the standard library. Although its implementation is not public, Nate discovered that it is used to increase the index of a collection. Given this, it may not be a good idea to use the same operator for a completely different purpose. You can choose a different one. The actual implementation is not important. All we have to do is declare the operator and implement the methods, which are just delegates to the methods we already have, greaterThan and lessThan:
Thus, our switch statement becomes:
Again, there is no space between operators and operands. This is our limit, very close to the original plan, but obviously not perfect. Update 9/19: Joseph Lord reminded me that Swift has a similar syntax:
This syntax, although it may not be as concise as our custom solution, is definitely good enough because you shouldn't create a custom syntax for such a simple purpose. However, our solution is general and can be applied in different places. Read on. Other applications By the way, the solution presented here is very general. Our overloaded pattern matching operator ~= works with any type T and any method taking a T that returns Bool. In other words, our implementation makes pattern ~= value as useful as pattern(value). Furthermore, switch value { case pattern: ... } works as useful as if pattern(value) { ... }. Checking digital parity Let's take a look at a few examples. First, a simple example illustrates its applicability, although it has little practical significance. Suppose you have a method isEven that checks whether a number is an even number:
Now:
Can become:
Note that by default, the following code is invalid:
Matching Strings For a more practical example, let's say you want to match a string against a prefix or suffix. Let's start by writing two methods, hasPrefix and hasSuffix, that take two strings and check if the first argument is a prefix/suffix of the second. These are just variations of the existing standard library methods String.hasPrefix and String.hasSuffix, but with the arguments in a convenient order (prefix/suffix first, full string second). If you use Partially Applied Functions a lot and pass them to other methods, you'll find that you often need to repeat arguments to fit the parameters of the called method. Annoying, but not hard.
Now we can do this, which in my opinion is quite easy to read:
in conclusion To solve our original problem, we came up with a general solution that can solve a lot of different problems. I find this to be true all the time, when you pass methods around as values, it can be used in places you wouldn't normally think of. This is one of the core concepts behind the claim that functional programming improves composability. Extending Swift’s pattern matching system with new capabilities, both for built-in types and custom types, is extremely powerful. As always, be careful not to extend it too much. Even if a custom syntax looks cleaner than a more conservative solution, it can make the code harder to read for those who are not familiar with it. |
<<: Weekly crooked review: Let me give you tenderness with my chopped-off hands
>>: Some "pitfalls" and "solutions" for iOS game development and submission
We have heard countless rumors and lies since we ...
Recently, the number of new nucleic acid-positive...
At the Build conference yesterday, Microsoft rele...
[[329485]] According to foreign media reports, Ap...
Currently, Internet marketing has become the main...
Recently, according to DIGITIMES, as the penetrat...
[[171604]] Not long ago, I did an H5 project and ...
Looking up at the stars is the oldest human behav...
The James Webb Space Telescope (hereinafter refer...
Reviewing Chinese in the college entrance examina...
When it comes to product growth, one thing that m...
On February 10, the Global Mobile Internet Confer...
A black hole is an extremely powerful celestial b...
As we all know, Huawei has been suppressed and hi...
Today (February 4) It is the seventh day of the S...