Skip to content

Latest commit

 

History

History
154 lines (108 loc) · 7.06 KB

active-patterns.md

File metadata and controls

154 lines (108 loc) · 7.06 KB
title description ms.date
Active Patterns
Learn how to use active patterns to define named partitions that subdivide input data in the F# programming language.
05/16/2016

Active Patterns

Active patterns enable you to define named partitions that subdivide input data, so that you can use these names in a pattern matching expression just as you would for a discriminated union. You can use active patterns to decompose data in a customized manner for each partition.

Syntax

// Active pattern of one choice.
let (|identifier|) [arguments] valueToMatch = expression

// Active Pattern with multiple choices.
// Uses a FSharp.Core.Choice<_,...,_> based on the number of case names. In F#, the limitation n <= 7 applies.
let (|identifier1|identifier2|...|) valueToMatch = expression

// Partial active pattern definition.
// Uses a FSharp.Core.option<_> to represent if the type is satisfied at the call site.
let (|identifier|_|) [arguments] valueToMatch = expression

Remarks

In the previous syntax, the identifiers are names for partitions of the input data that is represented by arguments, or, in other words, names for subsets of the set of all values of the arguments. There can be up to seven partitions in an active pattern definition. The expression describes the form into which to decompose the data. You can use an active pattern definition to define the rules for determining which of the named partitions the values given as arguments belong to. The (| and |) symbols are referred to as banana clips and the function created by this type of let binding is called an active recognizer.

As an example, consider the following active pattern with an argument.

[!code-fsharpMain]

You can use the active pattern in a pattern matching expression, as in the following example.

[!code-fsharpMain]

The output of this program is as follows:

7 is odd
11 is odd
32 is even

Another use of active patterns is to decompose data types in multiple ways, such as when the same underlying data has various possible representations. For example, a Color object could be decomposed into an RGB representation or an HSB representation.

[!code-fsharpMain]

The output of the above program is as follows:

Red
 Red: 255 Green: 0 Blue: 0
 Hue: 360.000000 Saturation: 1.000000 Brightness: 0.500000
Black
 Red: 0 Green: 0 Blue: 0
 Hue: 0.000000 Saturation: 0.000000 Brightness: 0.000000
White
 Red: 255 Green: 255 Blue: 255
 Hue: 0.000000 Saturation: 0.000000 Brightness: 1.000000
Gray
 Red: 128 Green: 128 Blue: 128
 Hue: 0.000000 Saturation: 0.000000 Brightness: 0.501961
BlanchedAlmond
 Red: 255 Green: 235 Blue: 205
 Hue: 36.000000 Saturation: 1.000000 Brightness: 0.901961

In combination, these two ways of using active patterns enable you to partition and decompose data into just the appropriate form and perform the appropriate computations on the appropriate data in the form most convenient for the computation.

The resulting pattern matching expressions enable data to be written in a convenient way that is very readable, greatly simplifying potentially complex branching and data analysis code.

Partial Active Patterns

Sometimes, you need to partition only part of the input space. In that case, you write a set of partial patterns each of which match some inputs but fail to match other inputs. Active patterns that do not always produce a value are called partial active patterns; they have a return value that is an option type. To define a partial active pattern, you use a wildcard character (_) at the end of the list of patterns inside the banana clips. The following code illustrates the use of a partial active pattern.

[!code-fsharpMain]

The output of the previous example is as follows:

1.100000 : Floating point
0 : Integer
0.000000 : Floating point
10 : Integer
Something else : Not matched.

When using partial active patterns, sometimes the individual choices can be disjoint or mutually exclusive, but they need not be. In the following example, the pattern Square and the pattern Cube are not disjoint, because some numbers are both squares and cubes, such as 64. The following program uses the AND pattern to combine the Square and Cube patterns. It prints out all integers up to 1000 that are both squares and cubes, as well as those which are only cubes.

[!code-fsharpMain]

The output is as follows:

1 is a cube and a square
8 is a cube
27 is a cube
64 is a cube and a square
125 is a cube
216 is a cube
343 is a cube
512 is a cube
729 is a cube and a square
1000 is a cube

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. Additional arguments allow a general pattern to be specialized. For example, active patterns that use regular expressions to parse strings often include the regular expression as an extra parameter, as in the following code, which also uses the partial active pattern Integer defined in the previous code example. In this example, strings that use regular expressions for various date formats are given to customize the general ParseRegex active pattern. The Integer active pattern is used to convert the matched strings into integers that can be passed to the DateTime constructor.

[!code-fsharpMain]

The output of the previous code is as follows:

12/22/2008 12:00:00 AM 1/1/2009 12:00:00 AM 1/15/2008 12:00:00 AM 12/28/1995 12:00:00 AM

Active patterns are not restricted only to pattern matching expressions, you can also use them on let-bindings.

[!code-fsharpMain]

The output of the previous code is as follows:

Hello, random citizen!
Hello, George!

Note however that only single-case active patterns can be parameterized.

[!code-fsharpMain]

Struct Representations for Partial Active Patterns

By default, partial active patterns return an option value, which will involve an allocation for the Some value on a successful match. Alternatively, you can use a value option as a return value through the use of the Struct attribute:

open System

[<return: Struct>]
let (|Int|_|) str =
   match Int32.TryParse(str) with
   | (true, n) -> ValueSome n
   | _ -> ValueNone

The attribute must be specified, because the use of a struct return is not inferred from simply changing the return type to ValueOption. For more information, see RFC FS-1039.

See also