You may have heard of Active Patterns before – typically in conjunction with the words ‘awesome’ or ‘amazing’. Active Patterns are one of the more unique language features in F# and once you get a good feel for them one of the most powerful. This post will demonstrate Active Patterns from single-case and multi-case to partial and parameterized. If it’s called an Active Pattern, you’ll see an example in this post. So let’s get started.
First off is recognizing an Active Pattern. Active Patterns are what come inbetween ‘Banana clips’ or the ‘(|’ ‘|)’ symbols. Active Patterns can take different forms – each of which will be described in this post.
Single Case Active Patterns
The simplest type of Active Patterns are Single-case Active Patterns. Which are for converting the input into something different. A simple case is where you want to convert a string into an ALL UPPERCASE version. While you could use a regular function for this, Active Patterns let you use the result as part of a let or match statement.
Here are some examples:
// Single case Active Patterns - For converting data
let (|UpperCase|) (x:string) = x.ToUpper()
// The item being matched is the parameter into the active pattern, and what comes
// after the ActivePattern name is the result. In this case the pattern match will
// only fire if the AP result is exactly "FOO".
let result = match "foo" with
| UpperCase "FOO" -> true
| _ -> false
assert (result = true)
// This function simply returns the upper case version of the parameter. (Since
// the AP result was bound to the identifier 'result'.)
let ucName name = match name with UpperCase result -> result
// An example of converting a string to a color.
let (|ToColor|) x =
match x with
| "red" -> System.Drawing.Color.Red
| "blue" -> System.Drawing.Color.Blue
| "white" -> System.Drawing.Color.White
| _ -> failwith "Unknown Color"
// Use AP in let binding
let form = new System.Windows.Forms.Form()
let (ToColor col) = "red"
form.BackColor <- col
Multi-Case Active Patterns
A little more interesting are Multi-case Active Patterns. The best way to think about theses, are partitioning the entirety of the input space into different things. For example, dividing up all possible integers into Evens and Odds.
// Multi case Active Patterns - For dividing up the input space
let (|Odd|Even|) x = if x % 2 = 0 then Even else Odd
let isDivisibleByTwo x = match x with Even -> true | Odd -> false
// This active pattern divides all strings into their various meanings.
let (|Pharagraph|Sentence|Word|WhiteSpace|) (input : string) =
let input = input.Trim()
if input = "" then
elif input.IndexOf(".") <> -1 then
// Notice that Pharagraph contains an tuple of sentence counts, and sentences.
let sentences = input.Split([|"."|], StringSplitOptions.None)
Pharagraph (sentences.Length, sentences)
elif input.IndexOf(" ") <> -1 then
// Notice that Sentence contains an Array of strings
Sentence (input.Split([|" "|], StringSplitOptions.None))
// Notice that the word contains a string
let rec countLetters str =
match str with
| WhiteSpace -> 0
| Word x -> x.Length
| Sentence words
-> Array.map countLetters words |> Array.fold (+) 0
| Pharagraph (_, sentences)
-> Array.map countLetters sentences |> Array.fold (+) 0
Partial Active Patterns
Sometimes the input space is too large to have a single pattern divide it up entirely. In which case you can use a Partial Active Pattern. In short these are patterns which don’t always return something. The best way to think about this is in that these carve out and describe some sub-section of the input space. So over all natural numbers, only a few can be considered perfect squares or divisible by seven.
// Partial Active Patterns - For carving out a subsection of the input space.
let (|DivisibleBySeven|_|) input = if input % 7 = 0 then Some() else None
let (|IsPerfectSquare|_|) (input : int) =
let sqrt = int (Math.Sqrt(float input))
if sqrt * sqrt = input then
let describeNumber x =
| DivisibleBySeven & IsPerfectSquare -> printfn "x is divisible by 7 and is a perfect square."
| DivisibleBySeven -> printfn "x is divisible by seven"
| IsPerfectSquare -> printfn "x is a perfect square"
| _ -> printfn "x looks normal."
Parameterized Active Patterns
So far so good. But what if your Active Pattern needs additional information? A Parameterized Active Pattern is simply an active pattern that takes additional parameters. Notice though in match statements that only the last ‘thing’ mentioned is the result or output of the active pattern. All other things are parameters and inputs into the Active Pattern.
In the example we define a Partial Active Pattern that matches whether or not the input is a multiple of the parameter.
// Parameterized Active Patterns - Passing Patameters into Active Patterns
let (|MultipleOf|_|) x input = if input % x = 0 then Some(input / x) else None
let factorize x =
let rec factorizeRec n i =
let sqrt = int (Math.Sqrt(float n))
if i > sqrt then
match n with
| MultipleOf i timesXdividesIntoI
-> i :: timesXdividesIntoI :: (factorizeRec n (i + 1))
| _ -> factorizeRec n (i + 1)
factorizeRec x 1
assert ([1; 10; 2; 5] = (factorize 10))
So we’ve covered some simple examples of Active Patterns, but I know many of you are wondering what do you actually do with them? The answer will come in my next post, where I will show how to simplify Regular Expressions, XML Parsing, and Web Crawling all using Active Patterns.
Click the RSS feed and stay tuned J