Chapter 12

Pattern Matching & Active Patterns

Main Page

Introduction

Many times in programming we need to examine data structures and their values, and test them against one or more conditions. When these conditions are met, we perform certain computations, trigger transformations, or take particular actions. To accomplish these types of operations in F#, you can use a series of if…then…else statements; however, pattern matching offers greater power and flexibility, and goes well beyond more primitive if…then…else constructs.

Patterns

Before we can talk about pattern matching, it makes sense to first define what patterns are. Patterns are rules for transforming data. We use patterns throughout F# to do the following types of things:

·         compare data with a logical structure

·         decompose data into its constituent parts

·         extract information from constructs in various ways

Whereas other languages provide basic value-based decision and branching mechanisms, patterns augment and improve upon this ability. They provide the basis for making these same types of decisions based on the structure of the data and its type we well. This ability enables you to elegantly deconstruct and extract information from varying constructs in a clean, efficient, and succinct manner – something that is difficult to do in more traditional languages.

Pattern Matching

Pattern matching is the act of comparing a test expression with one or more patterns. F# is quite flexible with respect to the types of test expressions it’s capable of matching against. These include literals, constants, single variables, multiple variables, tuples, array, lists, enumerations, discriminated unions, records, and classes (to be covered) – and combinations thereof. For the rest of this chapter, we’ll look at how pattern matching works, and the types of things you can do with it.

Using match

The workhorse of pattern matching is the match expression, which combines branching control with computation. The match expression takes the following general form:

match test-expression with
    | pattern1 [ when condition ] -> result-expression1
    | pattern2 [ when condition ] -> result-expression2
    | ...

Throughout this section, I will use the terms as specified in the general form above. To be clear, let me explain the various components:

·         match expression. This is the entire construct, from the word match to the end of the final branch (the code after the vertical bars (|)).

·         test-expression. This is the input to the match expression. It may be a numeric constant, character, a string, a manifest constant, a simple variable, an array, etc. Most of F#’s types can be used as test-expressions.

·         pattern. This is the contour of symbols against which the test-expression is evaluated. The pattern specifies how to try to deconstruct the test-expression.

·         when condition. This is a predicate that qualifies a pattern match. In order for a match to be considered successful, the pattern must satisfy the test-expression and the when condition, if present (it’s optional) must evaluate to true.

·         result-expression. This is the expression that F# returns when the corresponding pattern is considered to be a match for the test-expression under consideration.

Pattern matching via the match expression provide a “small language” to describe the structure and the types of values you want to match against. I find it helpful to think of patterns as distant cousins to regular expressions.

When the match expression executes, F# compares the given test-expression with each of the specified patterns in turn. When the test-expression matches a pattern, the corresponding result-expression is evaluated and it returns a value. This value is the result of the entire match expression.

Once a match is found, F# stops evaluating. This means that you need to be aware how you order your patterns – you want to put the most specific patterns earlier in the pattern chain, and the more general ones towards the end; otherwise, the later ones will never be tested. The entire match expression is limited to a single type, meaning that all result-expressions must eventually resolve to the same type.

Matching Anything - The Wildcard Pattern

The simplest of patterns is called the wildcard pattern. The wildcard pattern is specified as a single underscore character (_) and matches any test-expression. Thet

match 3 with

   | _ -> printfn "wildcard matches anything."

 

match "f#" with

   | _ -> printfn "strings work, too"

 

match [1;2;3] with

   | _ -> printfn "arrays work fine"

You can match the wildcard pattern againt all legal test-expression types, and it will always yield a postive match.

Since the wildcard pattern “matches anything”, it’s normally used as the very last pattern in match expressions. It’s F#’s way of dealing with the fact that the test-expression matched none of the other patterns. Using a wildcard pattern prevents F# from needing to throw a MatchFailureException when it exhausts its search for a positive match; instead, it will reach the wildcard pattern and return the default expression.

Matching Constants

We can match constant test-expressions against constant patterns.  A constant pattern only matches when it equals the test-expression.

// A constant pattern only matches when it equals the test-expression

match 3 with

   | 2 -> "Never matches."

   | 3 -> "OK.  This matches."


In this example, F# compared the test-expression 3 with the pattern 2, found no match, and continued down the list of patterns in lexical order. When it found the pattern 3, it calculated a valid match, and evaluated the corresponding result-expression, "OK.  This matches." The return type of the match expression is string.

 

Although this example works fine, you should be aware that if you compile or run it in the F# Interactive Console, F# will report the following warning:

Program.fs(4,11): warning FS0025: Incomplete pattern matches on this expression. For example, the value '0' may indicate a case not covered by the pattern(s).

This means that F# has detected that it’s possible for a caller to pass in a test-expression that does not have match any of the patterns specified. We can handle this by using the wildcard pattern as a catch-all pattern:

match 3 with

   | 2 -> "Never matches."

   | 3 -> "OK.  This matches."

   | _ -> "Catch-all. Will never get here."   

Overall, this isn’t a very compelling example, and if all you could do was pattern match over literals, pattern matching wouldn’t be very useful. Let’s look at how symbols and manifest constants are used in conjunction with matching.

Matching Manifest Constants

In the following code, we define 2 manifest constants, GOLDENRATIO and LOG10. We then want to write match expression that uses these constants as patterns.

[<Literal>]

let GOLDENRATION = 1.618

[<Literal>]

let LOG10 = 2.303

 

let foo x =

    match x with

       | GOLDENRATION -> "The Golden Ratio"

       | LOG10 -> "Log 10 is Used Everywhere"

When you want to match against a manifest constant, you need to  tell F# that the pattern is actually a literal and not a symbol pattern.  You do this using the LiteralAttribute. If you fail to specify [<Literal>], the pattern matching engine will match the test-expression against any symbol and will always match the first pattern it finds.  Here is the same example without the benefit of the [<Literal>] attribute.

let GOLDENRATION = 1.618

let LOG10 = 2.303

 

let foo x =

    match x with

    | GOLDENRATION -> "The Golden Ratio"

    | LOG10 -> "Log 10 is Used Everywhere"

   

let result = foo 1.23

When run in the F# Interactive Console, we get the following result:

val GOLDENRATION : float = 1.618
val LOG10 : float = 2.303
val foo : 'a -> string
val result : string = "The Golden Ratio"

The test-expression 1.23 has somehow matched the GOLDENRATIO pattern, even though the values are not equal. What happened? The pattern matching engine didn’t know it was supposed to match 1.23 against the value 1.618; therefore, it treated GOLDENRATIO as a new unbound local symbol which can bind to anything. In fact, the expression let result = foo "abc" would have worked, too! This is probably not the way we want the match to behave.

The moral of the story is when your patterns are manifest constants, make sure to have attributed these constants with [<Literal>]. When used in this way, pattern matching is akin to a basic switch statement in C-based languages.

Just to show pattern matching using different data types, let’s look at another example that uses strings for the test-expression and patterns.

// Patterns and test expressions can be strings, too.

match "hello" with

   | "goodbye" -> "Never matches."

   | "hello" -> "Always matches."

   | _ -> "We'll never get here."

Matching Symbols

Patterns can be symbols as well. Symbols match the “shape” of the test-expression data. In the following example, the pattern x will always match the test-expression 3, since the single value 3 can be positively bound to the pattern x – they “match up” in structure.

// Patterns always match with a symbol that's not an enum, DU, or literal constant

match 3 with

   | x -> "This always matches here."

   | 3 -> "This would match, but we never get here!"

   | _ -> "This would match, but we never get here either!"


Value Binding

When comparing a test-expression against a pattern, F# automatically assigns the test-expression’s value(s) to the symbol(s) specified in the associated pattern. For example, when we pattern match against 3 and x above, x is initialized with the values from the test-expression, 3.

We call this automatic value-to-symbol association value binding. In general, value binding is a powerful by-product of matching because it enables the subsequent use of the values in downstream calculations. This economy of expression makes for succinct, expressive code. It is used quite naturally in many pattern matching scenarios.

When symbols are used as patterns, they can be leveraged in result-expression calculations and as return values:

match 3 with

  | x -> x + 2       // returns  5

  | 3 -> 6           // we'll never get here, and never return 6

  | _ -> 7           // we'll never get here, and never return 7

match expressions can act surprisingly when symbol patterns use the same name as other values that are in scope. Let’ look as a few examples:

// Local value x
let x = 5

match 3 with

   | x ->// returns 5!

 

 

// Returns 5, because x is not a local pattern symbol. y is, so y is bound to 3,
// but not subsequently used. We might as well have used a wildcard instead of y
// here.

 

Qualifying Matches Using When Guards

F# enables us to augment patterns with conditions called when guards. When guards are Boolean checks, a.k.a., predicates, which allow us to qualify patterns. When guards are used in conjunction with patterns to constrain what constitutes positive matches. The following example demonstrates their use:

let sign x =

    match x with

    | x when x < 0 -> -1    // match only when x < 0

    | x when x = 0 -> 0     // match only when x = 0

    | x when x > 0 -> 1     // match only when x > 0

    | _ -> int nan          // nan = "not a number" from System.Double.NaN 

With  when guards, the wildcard pattern is sometimes used so that the guts of the pattern match is based on the guard condition. It is not uncommon, for example, to see a function like sign (above) implemented like this:

let sign x =

    match x with

    | _ when x < 0 -> -1    // match only when x < 0

    | _ when x = 0 -> 0     // match only when x = 0

    | _ when x > 0 -> 1     // match only when x > 0

    | _ -> int nan          // nan = "not a number" from System.Double.NaN 

Here, we use the wildcard pattern to match whatever test-expression is passed into the match expression. We then base a positive match on the when guard’s return value.

We can leverage when guards as part of any pattern test. In the following example, we use a local binding in conjunction with a when clause. Note that the when clauses uses the bound value of x, not the externally defined x:

let x = 5

match 3 with

   | x when x = 5 ->// here x=3, not 5, so we fail the when clause

   | _ ->// this match succeeds and returns 5 for the match expression

Matching Patterns and Types

Patterns are not limited to single literals or variables. We can use patterns to match against tuples, list constructs, arrays, and various user-defined types. The following examples demonstrate using pattern matching with the types we’ve covered so far.

Matching Multiple Variables

Our test-expressions can contain more than one variable, as can the patterns we match against. In the following example, our test-expression contains two variables – an int and a string – and returns a Boolean.

let allowAccess userid pwd =

    match userid, pwd with

    | 123, "123"    -> true

    | 505, "xyz"    -> true

    | _, "**9"      -> true

    | 411, _        -> true

    | _             -> false

   

Matching Tuples

F#’s pattern matching engine is also capable of matching against tuples. The following function accepts a tuple of type bool  * bool and uses the tuple as its test-expression.

let orTable(b1, b2) =

    match(b1, b2) with

    | (true, true) -> true

    | (true, false) -> true

    | (false, true) -> true

    | (false, false) -> false

In this example, we did not need to provide a default branch, since we’ve covered all possible pattern combinations. F# recognizes that our patterns are logically complete, so does not issue a warning or an error.

Matching Arrays

When we match using an array pattern, in order to match successfully, the test-expression must be an array of the same size and makeup as the pattern being evaluated, and must contain the elements in the same order as the candidate pattern.

let digitmsg a =

    match a with

    | [|0;1|]     -> "binary digits"

    | [|2;4;6|]   -> "some even digits"

    | _ -> "who knows?"

If we were to call digitmsg with [|0; 1|], we’d get a postive match, whereas calling it with [|1;0|] would result in a non-match.

The patterns used with the match expression must all be of the same type. For example, the following produces a type-based error:

// Match array (error)

let digitmsg a =

    match a with    

    | [|2; 4; 6|]    -> "some even digits"

    | [|"test"|]     -> "does it work?"      // error: used string, expected int

    | _ -> "who knows?"

Note that we can use the wildcard pattern to match individual array elements:

let flower a =

    match a with

        | [|"roses"; _|]    -> "roses are red"

        | [|_; "violets"|]  -> "violets are blue"

        | [|_; _|]          -> "this matches any two things"

        | _                 -> "catch-all"

Matching Enums

You can use pattern matching to match against enumerations. The enumeration pattern must use the entire qualified name:

type Protocol =

    | UDP = 0 | TCP = 1

   

let sendPacket proto packet =

    match proto with

    | Protocol.UDP -> "sending UPD"

    | Protocol.TCP -> "sending TCP"

    | _ -> "unknown protocol"

Matching Lists

F# also supports working with list-based patterns.  The lists can be processed using literals, wildcards, and recursion:

// Lists can be matched as literals
match [1;2;3] with

   | [1;2;4] -> "doesn't match"

   | [3;2;1] -> "doesn't match, order counts"

   | [1;2;3] -> "OK - a match"

   | _ -> "should never get here"

 

// The contents of the list can be any valid type, e.g., strings or tuples.
// Remember that all list elements must be of the same type, e.g., all strings.

let famousDuo pair =

    match pair with

    | ["bonnie"; "clyde"]       -> true

    | ["adam"; "eve"]           -> true

    | ["ozzie"; "harriet"]      -> true

    | ["gilbert"; "sullivan"]   -> true

    | _                         -> false

   

let isFamous = famousDuo ["adam"; "eve"]

    // order matters. ["eve"; "adam"] would return false.

Lists can be matched with missing literals, by using a wildcard

match [1;2;3] with

   | [1;_;4] -> "doesn't match in the last element 3 vs. 4"

   | [1;2;_] -> "OK - the underscore matches to 3"

   | [_;2;3] -> "we'll never get here, but this would match if we did"

   | _ -> "should never get here"

 

// Symbols can be used in patterns to match part of a list

match [1;2;3] with

   | [1;x;4] -> "Doesn't match in the last element 3 vs. 4"

   | [1;2;x] -> "OK - the x is bound to 3 - in this line only!"

   | [x;2;3] -> "We'll never get here, but this would match if we did"

   | _ -> "Should never get here"

As you’ll recall, when we process lists, we normally do so recursively. To support recursive pattern matching on lists, F# supports two additional patterns:

·         []. This is the empy-list pattern. It used to test a list to see if it’s empty.

·         pat::pat. This is the cons pattern and is used to match the head and tail of the list.

In the following example, we create a recursive function that counts the number of elements in the given list. Although contrived, this example helps demonstate the empty-list and cons patterns:

let rec count lst =

    match lst with

    | [] -> 0                   // empty list -> 0 elements

    | [_] -> 1                  // any one thing -> 1 element

    | [_;_] -> 2                // any two things -> 2 elements

    | _ :: t -> count t + 1     // cons pattern. count recursively.

Let’s take a look at another list-based example that uses the cons pattern to sum up all the numbers in the collection:

let rec sum theList =

    match theList with

    | [] -> 0  

    | h :: t -> h + sum t

   

let theSum = sum [0..2..100]    // 2550

Using pattern matching in conjunction with recursive list processing is a common idiom in F# programming.

Matching Records

The F# pattern matching engine also recognizes records. To work with pattern matching records, you can match on individual record fields or combinations thereof. In the following example, we define a record type Employee and a function calcRaise. We then use various combinations of patterns to calculate raises based on a various criteria.

type Employee = {

    rank: string;

    name: string;

    salary: float

}

 

let calcRaise emp =

    match emp with

    | { rank="manager"; name="Smith" } -> emp.salary * 1.10

    | { rank="manager"} when emp.salary <= 75000.0 -> emp.salary * 1.20

    | { rank="indi"} -> emp.salary * 1.30

    | _ -> 0.0

   

 Matching Discriminated Unions

Last but not least, let’s take a look at using pattern matching in conjunction with DUs. In this next example, we demonstrate how to match against a DU’s discriminator using a variety of techniques. We also demonstrate a somewhat common idiom of nesting match expressions:

type Weapon =

    | Knife of int | Sword of int | Mace of int * bool

   

let k = Knife(100)

let s = Sword(500)

let m = Mace(750, true) // true=is magic

 

let showDamage w =

    match w with

       | Knife(damage) -> printfn "knife caused %d damage points" damage

       | Sword(_) -> printfn "sword caused 1000 damage points"

       | Mace(damage, isMagic) ->

          match isMagic with

             | true -> printfn "mace caused %d damage points" (damage * 5)

             | false -> printfn "mace causes %d damage points" damage

       

showDamage k

showDamage s 

showDamage m

The id Pattern

F# also supports the id pattern, which is a mechanism for explicitly invoking value binding. In the following example, we use the id pattern to make a copy of a list, binding the list’s values to new identifiers, by position:

let lst = [1; 2; 3]

let [x; y; z] as lst2 = lst

 

The id pattern is actually used with let – and not match. It also includes using the keyword as, as shown above. You can think of the id pattern as being invoked via let…as. After executing the above example, x = 1, y = 2 and z = 3. If the lists were of different sizes, F# would have generated a compile error.

Pattern Groups

In addition to being able to perform single and qualified matches, e.g., with when guards, we can also group matchues using “or” and “and” patterns. First, let’s look at “or” patterns, which allow us to chain together alternative patterns and pick among them. The “or” pattern uses the verical bar (|) to separate its alternatives. The following example uses an “or” pattern to match countries with currencies:

// Matching one of several alternatives with "or"

let currencyForCountry (country: string) =

    match country.ToLower() with

    | "us" | "east timor" | "ecuador" | "panama" -> "U.S. Dollar"

    | "portugal" | "spain" | "austria" | "belgium" -> "Euro"

    | _ -> "unknown currency"


In this example, we explicitly declare the type of the argument country to be string. We need to do this so that the call to country.ToLower() compiles correctly; otherwise, the F# compiler complains that it cannot fully determine country’s type, and thus cannot guarantee that the ToLower function exists.

In addition, when we explicitly declare function argument types, as in this example, we must surround the arguments and their types with parens to ensure F# evaluates them properly. If we omit the parens, F#’s order of evaluation rules cause it to consider country a generic vs. its assigned type.

To complement the “or” pattern, F# offers the “and” pattern. The “and” pattern requires that the test expression matches two or more separate patterns. The types on both sides of the “and” pattern must be compatible. The “and” patterns uses the ampersand (&) symbol to join the associated patterns. The following example was taken from the MSDN documentation on patterns:

let detectZeroAND point =

    match point with

    | (0, 0) -> printfn "Both values zero."

    | (var1, var2) & (0, _) -> printfn "First value is 0 in (%d, %d)" var1 var2

    | (var1, var2)  & (_, 0) -> printfn "Second value is 0 in (%d, %d)" var1 var2

    | _ -> printfn "Both nonzero."

   

We can then call the detectZeroAND function like so:

detectZeroAND (0, 0)

detectZeroAND (1, 0)

detectZeroAND (0, 10)

detectZeroAND (10, 15)

Alternative Functional Representation of match

F# offers another, shorthand form of pattern matching syntax – and it does not use the match keyword. Instead, this form of pattern matching uses the keyword function to introduce a lambda expression (the “pattern matching function”) in which the matching is performed immediately on the lambda’s argument.

In the following example, we create a pattern matching function that accepts a string representing a meal, and returns the corresponding price as a floating point value:

let menuPrice = function    // implicit un-named parameter

    | "breakfast" -> 3.49

    | "lunch"     -> 8.75

    | "dinner"    -> 18.50

    | "drink"     -> 2.50

    | _           -> 0.0

   

let cost = menuPrice "lunch" + menuPrice "drink"

Note that the function’s argument is implicit and its type is inferred by the type of the patterns (a string in this example). Because the argument is implicit, there can be only one.[1]

If we want to use the lambda pattern matching syntax, and still need to pass multiple values into our test-expression, we can use tuples or other aggregate data structures like discriminated unions. In the following example, we use a tuple to represent a point in some arbitrary square whose upper-left point is (0,0) and whose lower right point is (1.0, 1.0).

let isPointInSquare = function

    | (0.0, 0.0) -> false

    | (1.0, 1.0) -> false

    | (a, b) when a > 0.0 && a < 1.0 && b > 0.0 && b < 1.0 -> true

    | _ -> false

As demonstrated here, we have access to the implied lambda argument. We can see this clearly via the (a, b) pattern in this example. When we specify the pattern (a, b), value binding maps the lambda’s input values to the labels a and b respectivley.

Active Patterns

With the pattern matching mechanisms we’ve discussed so far, the test-expressions and the patterns have been completely static, i.e., they are fixed at compile time and do not change during the execution of the program.

What if we’d like to make our patterns “come alive” so that we could programmatically control and change them during the process of matching? Well, we can via active patterns. The fact that these patterns can change under program control is what makes them “active.”

Simple Transformations

The simplest form of an active pattern acts as a transformation. It accepts a single argument as input and transforms it in some way. The transformed value is the pattern used in the match expression. In the following example, we use an active pattern that converts its input, assumed to be a temperature in Fahrenheit, to the equivalent Celsius measure:

let (|F2C|) f =

    (f - 32.0) * (5.0 / 9.0)

This active pattern looks exactly like a regular function. The only difference is how the function’s name is bracketed by a set of vertical bars, colloquially called “banana clips.” The banana clips tell the F# compiler that we intend to use this function (active pattern) in conjunction with pattern matching, and that it’s not a general purpose function to be called arbitrarily. We need active patterns to implement this type of behavior because regular functions cannot be used as pattern candidates.

So, active patterns are like functions called in the context of pattern matching. We place them lexically where we normally place static patterns; however, when F# sees an active pattern vs. a static pattern, it invokes the active patternand uses the function’s result as the pattern value.

We can see how to use our F2C active pattern in the following example:

let test (f: float) =

     match f with

     | F2C c when c > 30.0 -> printfn "it's hot %f" c

     | F2C c when c > 20.0 -> printfn "it's temperate %f" c

     | F2C c when c > 10.0 -> printfn "it's cool %f" c

     | _ -> printfn "it's getting cold!"

Notice here we’re using our active pattern F2C where we’d use a normal, static pattern. The variable f is used as the input to active pattern, and c is the output. To process the active pattern, the match expression compares the value of f with the result of calling F2C c, and prints out a corresponding message.

Arbitrary Decomposition

In addition to converting single values, we can use active patterns to decompose most any data structure in a customized manner. To perform custom decomposition, we define “named partitions” that subdivide the data using criteria that we define. We can then use these named partitions in subsequent matching clauses. Active patterns come in two flavors: complete active patterns and partial active patterns. We will look at each one in turn.

Complete Active Patterns

While it is very convenient to think of active patterns as functions, there’s one wrinkle. Whereas functions have a single name, active patterns can have multiple names, or more accurately, multiple identifiers. Active patterns with single names, a.k.a., single identifiers, such as F2C above, are more formally known as single-case active patterns. In the general case, active patterns can include more than one identifier and are known as multi-case active patterns. Single-case and multi-case active patterns that use all of their inputs are called complete active patterns.

Let’s take a look at the following example, where we define a complete, multi-case active pattern:

let (|Child|Teen|Adult|Senior|) age =

    if age < 13 then Child

    elif age >= 13 && age < 18 then Teen

    elif age > 18 && age < 60 then Adult

    else Senior

This active pattern includes 4 identifiers: Child, Teen, Adult, and Senior. It is often helpful to think of these as “buckets” or partitions into which we’ll assign inputs. We also define an input argument, age, that we use as the basis of the partitioning. In the following example, we use this active pattern to process a list of people’s ages:

let rec showPeople p =

    let mutable tail = []

    match p with

    | [] -> printfn "<done>"

    | h :: t ->

        printf "%d = " h

        tail <- t

        match h with

        | Child  -> printfn "child";   

        | Teen   -> printfn "teen";    

        | Adult  -> printfn "adult";   

        | Senior -> printfn "senior";  

        showPeople tail

We can now test the showPeople function as follows:

showPeople [4; 10; 42; 15; 34; 19; 66; 48; 55; 32; 21; 18; 81; 74; 65; 14]

The thing that makes these patterns “active” is the fact that a function executes in order to partition the input.

Note that an active pattern identifier must begin with an uppercase character. This is one of the few places where F# dictates case outside of its keywords.

Partial Active Patterns (Some and None)

Although it may sounds complex, a partial active pattern is a special case of a single-case active pattern that returns an option type, i.e., it either returns Some or None. A partial active pattern is characterized by the fact that it returns an option and F#’s pattern matching mechanisms recognize that a Some characterizes a valid match, while a None does not ; otherwise, it behaves exactly like the active patterns we’ve discussed already.

T o define a partial active pattern, you append a wildcard character (_) to the end of the identifier inside the banana clips.  The following example, taken from an article by Pickering, demonstrates a partial active pattern used to match different date formats:

open System

 

let invar = Globalization.CultureInfo.InvariantCulture

let style = Globalization.DateTimeStyles.None

 

let (|ParseIsoDate|_|) str =

     let res, date = DateTime.TryParseExact(str, "yyyy-MM-dd", invar, style)

     if res then Some date else None

 

let (|ParseAmericanDate|_|) str =

     let res, date = DateTime.TryParseExact(str, "MM-dd-yyyy", invar, style)

     if res then Some date else None

 

let (|Parse3LetterMonthDate|_|) str =

     let res, date = DateTime.TryParseExact(str, "MMM-dd-yyyy", invar, style)

     if res then Some date else None

 

let parseDate str =

    match str with

    | ParseIsoDate d -> d

    | ParseAmericanDate d -> d

    | Parse3LetterMonthDate d -> d

    | _ -> failwith "bad date"  // throws exception (covered later)

 

let d1 = parseDate "05-23-1978"

let d2 = parseDate "May-23-1978"

let d3 = parseDate "1978-05-23"

let d4 = parseDate "05-23-78"   // shoud fail

In this example, we use partial active patterns to parse a given date string. Since the string may be in one of several formats, we define several active patterns, one per date format.

Active patterns are also useful when implementing “and” patterns. Recall from Chapter 11 that “and” patterns consist of multiple match clauses. To be considered a successful match, the input must satisfy all of the clauses.  In the following example, we use active patterns to succinctly implement an “and” pattern to ensure that a given number is a both a multiple of 3 and is even:

let (|MultipleOf3|_|) n = if n % 3 = 0 then Some(n) else None

let (|Even|_|) n = if n % 2 = 0 then Some(n) else None

 

let checkNum x =

    match x with

    | MultipleOf3 a & Even b -> printfn "%d is a multiple of 3 and is even." x

    | _ -> printfn "%d is just %d" x x

A couple of important things to note. First, remember that every active pattern receives at least one input – the test-expression in the match expression. In the checkNum example above, the test-expression is x.  Second, when a pattern returns a value, we capture it in an output variable. This is the purpose of the a and the b in the checkNum function above.  If we omit a, for example, the compiler will complain that we have an expression of type unit being used with an expression of type int. In other words, the pattern is returning a value (the int) and we’ve no place to put it.

The placement of active pattern inputs and output can be a little bit confusing. We’re used to specifiying input arguments explicitly and placing them after the name of the function. In the case of active patterns, the input is implicit via the test-expression, and the variable following the active pattern name is actually the output (the result of the active pattern executing).

Parameterized Active Patterns

Active patterns accept at least one input argument – the test-expression a.k.a., the data being matched via the match expression. Active patterns can accept additional arguments as well, in which case they are called parameterized active patterns. These additional arguments allow us to constrain and/or specialize the processing our active patterns performs. In the following example, we define an active pattern that enables us to parse a substring from an input string:

let (|StringMatches|_|) (mytarget : string) (mystring : string)  =

    if (mystring.Contains(mytarget)) then Some(true) else None

   

We can use this active pattern as follows:

let stringContains str target =

    match str with

    | StringMatches target result -> printfn "%s contains %s" str target

    | _ -> printfn "Substring not found"

Here again, I’d like to emphasize the somewhat confusing placement and binding of active pattern arguments. In this example, the test-expression str serves as input to the StringMatches active pattern. The match’s test-expression always binds to the last (right-most) parameter in the the active pattern. This means that in this example, str binds to the active pattern’s mystring argument. In addition, the target argument from the match expression binds to the mytarget argument in the active pattern, and the result variable binds to the active pattern’s Some/None return value.

What You Need to Know

·         Patterns are rules for mapping and transforming input data.

·         F# uses pattern matching to map patterns to values.

·         The match expression is used to invoke pattern matching. The match expression is like a set of if…then…else or switch statements, but more powerful.

·         You use the wildcard pattern (_) to match “anything.”

·         With the match expression, you can match constants, single values, multiple values, tuples, arrays, lists, and cons (::). You can also match records, discriminated unions, object types/constraints, and nulls.

·         You can combine match expressions with “or” and “and” constructs.

·         You can use when guards to augment patterns with predicates.

·         There is an alternative form of the match expression that uses a lambda via the function keyword. This lambda accepts a single, implicit argument available to the body of the lambda.

·         Active patterns enable us to define a function that F# calls when pattern matching. The result of the function is used as the pattern F# uses to match against the test expression.

 



[1] Like in Highlander, the movie.

 

Feedback

We welcome your feedback. If you have comments or questions about this chapter, please feel free to e-mail us at

Keep Reading

Next Chapter...