Introduction to F# Active Patterns
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
WhiteSpace
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))
else
// Notice that the word contains a string
Word (input)
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
Some()
else
None
let describeNumber x =
match x with
| 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
[]
else
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
Comments
Anonymous
February 21, 2008
PingBack from http://msdnrss.thecoderblogs.com/2008/02/21/introduction-to-f-active-patterns/Anonymous
February 22, 2008
In my last post I introduced Active Patterns. Sure they seem neat, but what do you actually do with them?. This post coverage how to leverage Active Patterns to make your code simpler and easier to understand. In particular, how you can use Regular ExpressionsAnonymous
May 30, 2008
Last Tuesday I gave a talk to the .NET Developers Association entitled Language Oriented ProgrammingAnonymous
November 25, 2008
这里先是介绍了F#中模式匹配的用法,这个可以理解为使用F#内置的模式,这样我们就可以处理F#中的值和特定的数据结构,比如列表、Union类型和元组等;接下来更进一步,活动模式把模式匹配的语法用到了其他更多的数据结构,这样模式的应用范围得到了很大的扩展。而且通过活动模式,我们可以将问题域转换为另一套术语来表达,这也就有了一些LOP(Language-Oriented Programming)的特点,事实上,活动模式正是F#中LOP的实现方式之一。