Partager via


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 Expressions

  • Anonymous
    May 30, 2008
    Last Tuesday I gave a talk to the .NET Developers Association entitled Language Oriented Programming

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