Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / F#

F#25 : Active Patterns

0.00/5 (No votes)
16 May 2014CPOL3 min read 16.9K  
Ok so we just wrapped up the final dedicated chunk of the main F# areas, where we looked at OO Programming in F#. These last couple of articles will be miscellaneous ones that I could no really find a home for. This one in particular will be on Active Patterns.

Ok so we just wrapped up the final dedicated chunk of the main F# areas, where we looked at OO Programming in F#. These last couple of articles will be miscellaneous ones that I could no really find a home for. This one in particular will be on Active Patterns.

Active pattern allow you to define a pattern that may be used to subdivide the input data, so that you may use regular pattern matching. You can think of active patterns as a way to decompose data in a custom manner to suit your current needs.

You define active patterns using the following sort of syntax

// Complete active pattern definition.
let (|identifer1|identifier2|...|) [ arguments ] = expression

// Partial active pattern definition.
let (|identifier|_|) [ arguments ] = expression

You may have one or ore identifiers, and you may have complete or partial active patterns. A complete one will need to match the entire input, whilst a partial one is a partially applied pattern that will match only when applied with the rest of the input data.

We will be examining the difference between complete active patterns and partial active patterns in this post, so have no fear.

A Simple Complete Active Pattern Example

Here is a very simple complete active pattern that simply examines an input value, and if the input value is > some threshold, will return one active pattern identifier, otherwise another. The active pattern identifiers are YOUR choice you would pick them to fit your domain.

//A very simple active pattern
let (|DiscountAuthorisationNeeded|DiscountApproved|) input =
   if input > 10 then
       DiscountAuthorisationNeeded else
       DiscountApproved

let ApplyDiscount input =
    match input with
    | DiscountAuthorisationNeeded -> printfn "%d : Wil need to be authorised" input
    | DiscountApproved -> printfn "%d : Discount approved" input

ApplyDiscount 7
ApplyDiscount 11
ApplyDiscount 32

Which when run gives the following output:

image

Another Complete Active Pattern Example

open System.Drawing
…
…

let (|RGB|) (col : System.Drawing.Color) =
    ( col.R, col.G, col.B )

let (|HSB|) (col : System.Drawing.Color) =
    ( col.GetHue(), col.GetSaturation(), col.GetBrightness() )

let printRGB (col: System.Drawing.Color) =
    match col with
    | RGB(r, g, b) -> printfn " Red: %d Green: %d Blue: %d" r g b

let printHSB (col: System.Drawing.Color) =
    match col with
    | HSB(h, s, b) -> printfn " Hue: %f Saturation: %f Brightness: %f" h s b

let printAll col colorString =
    printfn "%s" colorString
    printRGB col
    printHSB col

printAll Color.Red "Red"
printAll Color.Black "Black"
printAll Color.White "White"
printAll Color.Gray "Gray"
printAll Color.BlanchedAlmond "BlanchedAlmond"

Here is another example which I have shamelessly borrowed from MSDN, which shows how you can use active pattern to decompose an input in various different ways. The System.Drawing.Color is an excellent example of this, as it can be expressed in RGB or HSB formats. Here is what this code looks like when it runs:

image

Here is another example that allows you to match against the result of an active pattern

let (|Reversed|) (x:string) = new String(x.Reverse().Cast<char>().ToArray())

let TestActivePattern input = match input with
    | Reversed "rab" -> true
    | _ -> false 

printfn "TestActivePattern 'foo'=%A" (TestActivePattern "foo")
printfn "TestActivePattern 'FOO'=%A" (TestActivePattern "FOO")
printfn "TestActivePattern 'bar'=%A" (TestActivePattern "bar"

Which when run gives the following results:

image

Partial Active Patterns

Sometimes what you need is to only match part of the input, luckily F# also allows for this by way of partial active patterns. Partial active patterns do not always produce a result, and as such it is quite common to see them return Option types (Some X, None). To define a partial active pattern, you use a wildcard character (_) at the end of the list of patterns inside the banana clips. This is shown below.

let (|DivisibleBy10|_|) input = if input % 10 = 0 then Some() else None

let printNumberStats x =
    match x with
    | DivisibleBy10 -> printfn "%A is divisible by 10" x
    | _ -> printfn "%A is not divisible by 10" x

[1..20] |> List.iter printNumberStats

Which when run gives the following results:

image

Parameterized Active Patterns

Active patterns always take at least one argument for the item being matched, but they may take additional arguments as well, in which case the name parameterized active pattern applies. The canonical example here is to use a regular expression active pattern, which not only takes the regular expression pattern, but also returns the matches. Here is an example of that:

open System.Text.RegularExpressions

let (|RegExGroup|_|) pattern input =
let m = Regex.Match(input,pattern)
    if (m.Success) then Some m.Groups.[1].Value else None

let testRegex str =
    match str with
    | RegExGroup "cat" animal ->
        printfn "The value of %s is a cat" animal
    | RegExGroup "dog" animal ->
        printfn "The value of %s is a dog" animal
    | _ -> printfn "The value '%s' is not a beloved pet at all, get another pet hombre" str

testRegex "cat"
testRegex "dog"
testRegex "lizard"

Which when run gives the following results:

image

NOTE : I hate Regular Expressions, and I once read a post that said if you use Regular Expressions to fix a problem, you now have 2 problems, which I could not agree with more.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)