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
let (|identifer1|identifier2|...|) [ arguments ] = expression
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.
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:
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:
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:
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:
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:
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.