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 (4)

  1. 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 Expressions

  2. Last Tuesday I gave a talk to the .NET Developers Association entitled Language Oriented Programming

  3. Anders Cui says:

    这里先是介绍了F#中模式匹配的用法,这个可以理解为使用F#内置的模式,这样我们就可以处理F#中的值和特定的数据结构,比如列表、Union类型和元组等;接下来更进一步,活动模式把模式匹配的语法用到了其他更多的数据结构,这样模式的应用范围得到了很大的扩展。而且通过活动模式,我们可以将问题域转换为另一套术语来表达,这也就有了一些LOP(Language-Oriented Programming)的特点,事实上,活动模式正是F#中LOP的实现方式之一。