That’s a great question. There’s a lot of overlap, between the two, but there are some things you can do with a switch statement that you can’t do with pattern matching, and vice versa.
Let’s look at an example of where they overlap. One common way that people use switch
statements is to pick a choice from a set of potential options. For example, if you were writing a program to display a list of files, you might want to match specific file extensions and show a more human readable name for the file type. In a language with switch
statements you might say (in C-like pseudo-code):
string get_file_type_description(string file_name) {
string extension = get_file_extension(file_name);
switch (extension) {
case "png":
return "Image Fiile";
case "txt":
return "Plain text file";
default:
return (extension + " file");
In this example, we’re matching each branch of the switch
statement with an exact value, and returning a value based on that. In Haskell we can do the same thing with pattern matching. The usual way would be using a case
expression, which looks pretty similar to switch
:
getFileDescription :: FilePath -> String
getFileDescription fileName =
case getFileExtension fileName of
"png" -> "Image File"
"txt" -> "Plain text file"
extension -> extension ++ " file"
The most notable difference in this example is that when we’re using pattern matching, we don’t have keywords like case
and default
. Instead, each branch starts with the pattern we want to match against (in this case, the exact string we want to match on). Instead of a default
case, we just provide a variable that will end up holding whatever value is there if we didn’t match one of the other branches.
It’s a little surprising that the very common use case for switch statements and pattern matching with case
overlap so much, because in a lot of other ways they offer really different features. One of the main selling points of pattern matching is that it allows you to quickly access data that has a fairly complicated “shape”. Let’s look at another common example of pattern matching with case
: handling command line arguments.
In a larger application you might use a library to deal with command line arguments, but for short programs with only a couple of options, you might want to do it manually. Let’s say that you’re building a program to copy a file. You want to support a couple of different options for copying:
First, if the user passes in the --help
flag, you want to display a help message. Otherwise, they should pass in a source file, a destination file, and an option number of bytes to copy We can use pattern matching to make the process a lot easier:
getProgramOptions :: [String] -> Either String ProgramOptions
getProgramOptions commandLineArgs =
case commandLineArgs of
[inputFile, outputFile] ->
makeProgramOptions inputFile outputFile CopyAllData
[inputFile, outputFile, amount] ->
makeProgramOptions inputFile outputFile (CopySomeData amount)
_ -> showHelpText
In this example we’re using pattern matching to identify how many arguments were passed in, and to bind the value of those arguments to particular variables like inputFile
and outputFile
(the _
at the end says to match anything and ignore the values, like default
). This is really convenient because it lets us combine the common concerns of “check how many arguments were passed in” and “assign the arguments to a variable” into a single expression.
Another benefit of pattern matching is that you can use it in more places than you would want to use an case
expression. For example, you can commonly see pattern matching being used with functions. This function to recursively add up the numbers in a list is one example:
addNumbers [] = 0
addNumbers (thisNumber:rest) = thisNumber + addNumbers rest
In the first function definition, we match on the case where we have an empty list and return a value of 0. The second definition matches the first element of the list and the rest of the list into two parts. It’s safe, because we’ve already check for empty lists in the earlier version of the function.
Another case where pattern matching is useful is when you are defining values. For example, we can get each of the elements out of a tuple with pattern matching in a way that looks awfully similar to multi-return in languages like Go:
returnTwoNumbers = (5,7)
sumTwoNumbers =
let (x,y) = returnTwoNumbers
in x + y
Here, we’re using a pattern to make it easier for us to get at each of the values inside of the tuple. Without pattern matching we’d have to say:
sumTwoNumbers =
let numbers = returnTwoNumbers
in fst numbers + snd numbers
Of course, there are some situations where switch
can do things that pattern matching doesn’t do. For example, some languages let you embed an arbitrary expression in your switch statements:
string file_size_units(int bytes) {
switch(bytes)
case (bytes > 1024):
return "kb"
case (bytes > 1024 * 1024)
return "mb"
case (bytes > 1024 * 1024 * 1024)
return "gb"
default:
return "bytes"
}
There’s not a good way to use pattern matching for this in Haskell, but we can use a different feature, guard clauses to get the same ability:
fileSizeUnits bytes
| bytes > 1024 = "kb"
| bytes > 1024 * 1024 = "mb"
| bytes > 1024 * 1024 * 1024 = "gb"
| otherwise = "bytes"
Anyway, this response has gotten pretty long, but I hope it helps!