I wanted to do a quick post about active patterns in F#, specifically the usefulness of Single Total Active Patterns (STAPs?) for transforming and validating data.
One slightly annoying thing about the .NET BCL is the fact that there is no plain
Date type. Oftentimes you only care about dealing with the date portion of a
DateTime, and if there happens to be a stray time associated with it, any comparison might not work as expected.
In any case, I often want to work with the date and/or time portions separately, so I find myself doing things like this at the top of a method:
let date = dateTime.Date let time = dateTime.TimeOfDay
Of course, in F# we could condense this down to a single line with tuple destructuring:
let date, time = dateTime.Date, dateTime.TimeOfDay
Another approach is to define some active patterns to deal specifically with dates and times:
let (|Date|) (dateTime:DateTime) = dateTime.Date let (|Time|) (dateTime:DateTime) = dateTime.TimeOfDay
This allows us to rewrite the first snippet as follows:
let (Date date) = dateTime let (Time time) = dateTime
People coming from other languages might mistakenly think
Time are types in these declarations, but of course they are our active pattern names. The types of
time are inferred. Again, we can condense this by using an AND Pattern (
&) to combine the pattern matches.
let Date date & Time time = dateTime
Active patterns and pattern matching in general becomes more powerful when you realize that they're not just limited to
match/try ... with constructs, but every
let binding and parameter is also a pattern match. So one interesting thing we can do with active patterns is reshape our data directly in a parameter declaration. For example, suppose we have a function that takes a
let f (dateTime:DateTime) = ...
We could extract the date portion right in the parameter declaration:
let f (Date date) = ...
Or we could bind both the date and time to separate values:
let f (Date date & Time time) = ...
Note that from the caller's perspective this is still just one parameter. The only strange thing about this is that since our parameter is anonymous, we won't get intellisense for the name of the parameter, or the compiler will give it an auto-generated name like
_arg1 if it is in a separate assembly. I don't know of any fix for this other than to use a signature file.
The usage of active patterns on arguments opens up other possibilities such as validation. For example, suppose we were to define the following patterns:
let (|NonEmptyString|) value = if String.IsNullOrEmpty value then failwith "String must be non-empty." else value let (|GreaterThanEqual|) comparand value = if value < comparand then failwithf "Value must be greater than or equal to %A." comparand else value
Now we can use these to define preconditions on our function parameters:
let sayHello (NonEmptyString name) = printfn "Hello, %s." name let rec factorial (GreaterThanEqual 0 n) = if n = 0 then 1 else n * factorial (n - 1)
If we violate a precondition, such as by passing a
-1 to our
factorial function, then it will throw an exception. The only downside is that it doesn't include the argument name. We could pass the argument name as an additional argument to the active pattern, but then that is getting kind of crufty.
I realize some of these examples are probably somewhat contrived, and I'm not sure I would do validation like this in a real application. Still, I hope it expands your view of the ways in which active patterns might be used to write more concise and expressive code.