Haskell pattern matching - what is it?
What is pattern matching in Haskell and how is it related to guarded equations?
I've tried looking for a simple explanation, but I haven't found one.
EDIT: Someo开发者_高级运维ne tagged as homework. I don't go to school anymore, I'm just learning Haskell and I'm trying to understand this concept. Pure out of interest.
In a nutshell, patterns are like defining piecewise functions in math. You can specify different function bodies for different arguments using patterns. When you call a function, the appropriate body is chosen by comparing the actual arguments with the various argument patterns. Read A Gentle Introduction to Haskell for more information.
Compare:
with the equivalent Haskell:
fib 0 = 1
fib 1 = 1
fib n | n >= 2
= fib (n-1) + fib (n-2)
Note the "n ≥ 2" in the piecewise function becomes a guard in the Haskell version, but the other two conditions are simply patterns. Patterns are conditions that test values and structure, such as x:xs
, (x, y, z)
, or Just x
. In a piecewise definition, conditions based on =
or ∈
relations (basically, the conditions that say something "is" something else) become patterns. Guards allow for more general conditions. We could rewrite fib
to use guards:
fib n | n == 0 = 1
| n == 1 = 1
| n >= 2 = fib (n-1) + fib (n-2)
There are other good answers, so I'm going to give you a very technical answer. Pattern matching is the elimination construct for algebraic data types:
"Elimination construct" means "how to consume or use a value"
"Algebraic data type", in addition to first-class functions, is the big idea in a statically typed functional language like Clean, F#, Haskell, or ML
The idea of algebraic data types is that you define a type of thing, and you say all the ways you can make that thing. As an example, let's define "Sequence of String" as an algebraic data type, with three ways to make it:
data StringSeq = Empty -- the empty sequence
| Cat StringSeq StringSeq -- two sequences in succession
| Single String -- a sequence holding a single element
Now, there are all sorts of things wrong with this definition, but as an example it's interesting because it provides constant-time concatenation of sequences of arbitrary length. (There are other ways to achieve this.) The declaration introduces Empty
, Cat
, and Single
, which are all the ways there are of making sequences. (That makes each one an introduction construct—a way to make things.)
- You can make an empty sequence without any other values.
- To make a sequence with
Cat
, you need two other sequences. - To make a sequence with
Single
, you need an element (in this case a string)
Here comes the punch line: the elimination construct, pattern matching, gives you a way to scrutinize a sequence and ask it the question what constructor were you made with?. Because you have to be prepared for any answer, you provide at least one alternative for each constructor. Here's a length function:
slen :: StringSeq -> Int
slen s = case s of Empty -> 0
Cat s s' -> slen s + slen s'
Single _ -> 1
At the core of the language, all pattern matching is built on this case
construct. However, because algebraic data types and pattern matching are so important to the idioms of the language, there's special "syntactic sugar" for doing pattern matching in the declaration form of a function definition:
slen Empty = 0
slen (Cat s s') = slen s + slen s'
slen (Single _) = 1
With this syntactic sugar, computation by pattern matching looks a lot like definition by equations. (The Haskell committee did this on purpose.) And as you can see in the other answers, it is possible to specialize either an equation or an alternative in a case
expression by slapping a guard on it. I can't think of a plausible guard for the sequence example, and there are plenty of examples in the other answers, so I'll leave it there.
Pattern matching is, at least in Haskell, deeply tied to the concept of algebraic data types. When you declare a data type like this:
data SomeData = Foo Int Int
| Bar String
| Baz
...it defines Foo
, Bar
, and Baz
as constructors--not to be confused with "constructors" in OOP--that construct a SomeData
value out of other values.
Pattern matching is nothing more than doing this in reverse--a pattern would "deconstruct" a SomeData
value into its constituent pieces (in fact, I believe that pattern matching is the only way to extract values in Haskell).
When there are multiple constructors for a type, you write multiple versions of a function for each pattern, with the correct one being selected depending on which constructor was used (assuming you've written patterns to match all possible constructions--which it's generally good practice to do).
In a functional language, pattern matching involves checking an argument against different forms. A simple example involves recursively defined operations on lists. I will use OCaml to explain pattern matching since it's my functional language of choice, but the concepts are the same in F# and Haskell, AFAIK.
Here is the definition of a function to compute the length of a list lst
. In OCaml, an ``a listis defined recursively as the empty list
[], or the structure
h::t, where
his an element of type
a(
abeing any type we want, such as an integer or even another list),
tis a list (hence the recursive definition), and
::` is the cons operator, which creates a new list out of an element and a list.
So the function would look like this:
let rec len lst =
match lst with
[] -> 0
| h :: t -> 1 + len t
rec
is a modifier that tells OCaml that a function will call itself recursively. Don't worry about that part. The match
statement is what we're focusing on. OCaml will check lst
against the two patterns - empty list, or h :: t
- and return a different value based on that. Since we know every list will match one of these patterns, we can rest assured that our function will return safely.
Note that even though these two patterns will take care of all lists, you aren't limited to them. A pattern like h1 :: h2 :: t
(matching all lists of length 2 or more) is also valid.
Of course, the use of patterns isn't restricted to recursively defined data structures, or recursive functions. Here is a (contrived) function to tell you whether a number is 1 or 2:
let is_one_or_two num =
match num with
1 -> true
| 2 -> true
| _ -> false
In this case, the forms of our pattern are the numbers themselves. _
is a special catch-all used as a default case, in case none of the above patterns match.
Pattern matching is one of those painful operations that is hard to get one's head around if you come from procedural programming background. I find it hard to get into because the same syntax used to create a data structure can be used for matching.
In F# you can use the cons operator ::
to add an element to the beginning of a list like so:
let a = 1 :: [2;3]
//val a : int list = [1; 2; 3]
Similarly you can use the same operator to split the list up like so:
let a = [1;2;3];;
match a with
| [a;b] -> printfn "List contains 2 elements" //will match a list with 2 elements
| a::tail -> printfn "Head is %d" a //will match a list with 2 or more elements
| [] -> printfn "List is empty" //will match an empty list
精彩评论