Monthly Archives: June 2017

Partial Active Patterns

There are four kinds of active patterns. We’ve already looked at the Single and Multicase Active Patterns. We’ll now look at Partial Active Pattern.

Partial Active Pattern

A partial active pattern is used when the match expression needs to be transformed in different ways for the individual patterns. Partial active patterns are mutually exclusive in that they don’t necessarily have to relate to the other patterns.

We define the partial active pattern using the banana clips, a label, and an underscore. We identify a successful match by returning Some value and a non match with None.

let (|StartsWithCapital|_|) (v : string) = 
    if v.[0] |> System.Char.IsUpper then Some v
    else None

We can use the active pattern in our match expressions for readability.

let processFile (filePath : string) =
    filePath |> System.IO.File.ReadAllLines
      |> Seq.filter (function StartsWithCapital _ -> true
                            | _                   -> false)

Our processFile function takes in a file, reads all the lines, and then gives us a sequence of lines that begin with a capital letter. There is an issue though. We are using an indexer (v.[0]) on the string in our active pattern which will fail if the value is null or empty. Let’s add another partial active pattern.

let (|NullOrEmptyLine|_|) (v : string) = 
    if System.String.IsNullOrEmpty v then Some ()
    else None

The filter of the processFile will now look like:

Seq.filter (function NullOrEmptyLine     -> false
                   | StartsWithCapital _ -> true
                   | _                   -> false)

The order in which we place the pattern expressions matters. We have to do the EmptyLine pattern match before the StartsWithCapital or we will end up with the same error as before.

Putting it to use

We’ve received some specifications for interfacing with an old system. The system streams a sentence per line. A sentence is identified by the starting capital letter of the sentence. There are some issues with the system. Every once in a while the system will send a sentence in reverse or it will send garbage data (no starting or ending capital letter). Some lines may be null and they should be converted to newlines. Format the incoming data where all sentences are not reversed and include the empty lines.

Based on the specs above we have four cases:

  • Normal sentence
  • Reversed sentence
  • Empty line
  • Garbage data

We seem to already have 2 partial patterns for this exercise which means we only need one more to fulfill our requirements. The reversed sentence.

let (|EndsWithCapital|_|) (v : string) =
    if v.[v.Length - 1] |> System.Char.IsUpper then Some v
    else None

With the final partial active pattern in place we can setup our pattern match to fix the streamed data.

let reverseString : string -> string = 
    Seq.rev >> Seq.toArray >> (fun s' -> new string(s'))

let mungeLine = function
    | NullOrEmptyLine     -> Some "\n"
    | StartsWithCapital s -> Some s
    | EndsWithCapital s   -> s |> reverseString |> Some
    | _                   -> None

Taking my word that the funny syntax in the reverseString function actually reverses a string, we can see the mungeLine function uses our partial active patterns for validating the data. Partial active patterns have made out match expressive and readable to non coders.

Summary

Partial active patterns give us a means to do pattern matching when it may seem like the patterns have nothing in common. Partial active patterns allow us to transform the value we are matching before evaluating the pattern. We have not added partial active patterns to our collection of tools to create readable and expressive code.

Introducing Active Patterns

We’ve been working with pattern matching in F# for awhile and we are ready to dive into Active Patterns. There are 16 different patterns we can match on right out of the box. For the cases where the base patterns aren’t enough we can use Active Patterns to pick up the slack. There are four kinds of active patterns. We’ll begin by looking at two kinds of active patterns.

Single-Case Active Patterns

This kind of active pattern isn’t used all that often. A Single-Case Active Pattern can be thought of as a view or a subset of data from an object. This gives us the option of matching on some of the data or transforming the data into something useful.

#r "System.Windows.Forms"
open System.Windows.Forms

let (|Position|) (ctrl : Control) = ctrl.Left, ctrl.Top

let (|Size|) (ctrl : Control) = ctrl.Width, ctrl.Height

let (|PositionAndSize|) (ctrl : Control) = 
    match ctrl, ctrl with
    | Position p, Size s -> p, s

The System.Windows.Forms.Control class has over 30 properties. We can group related properties together using single-case active patterns. We’ve defined some patterns which can be used to match on properties we may be interested. We’ve created active patterns for Position, Size, and a combination of the two.

We can use our active patterns in other functions to make our code more expressive.

let printPosition ctrl =
    match ctrl with
    | Position (x,y) -> printfn "Left = %d, Top = %d" x y

We can use a pattern matching shorthand to make the function more concise.

let printPosition = function 
    | Position (x,y) -> printfn "Left = %d, Top = %d" x y

Multicase Active Patterns

The Multicase Active Pattern is more commonly used than the single-case active pattern. We partition the input domain into a known set of possible values. The multicast active pattern is a closed set of values and therefore a successful match must be returned. As of F# version 4.1 there is an upper limit of 7 values you can define in a multicase active pattern.

Let’s categorize scores in the game of darts. We can place a score into a finite set of possibilities. An invalid score is anything less than 0 or more than 180. We’ll mark scores of 150 and 180 as being special. Anything between 150 and 180 will be a high ton. A score between 100 and 150 will be classified a low ton. The rest will be standard points.

We’ll create a multicase active pattern to improve the readability of our pattern match.

let (|LowTon|Ton50|HighTon|Ton80|Points|Invalid|) x =
    if x < 0 || 180 < x then Invalid
    elif x = 150 then Ton50
    elif x = 180 then Ton80
    elif x < 100 then Points x
    elif x < 150 then LowTon x
    else HighTon x

This example isn't terribly unreadable. Creating the active pattern does allow us to define the potentially misunderstood code once and then use the clear and concise patterns throughout the rest of the application.

let printScore = function
  | Points pts  -> printfn "%d points." pts
  | LowTon pts  -> printfn "Low ton of %d points." pts
  | Ton50       -> printfn "One hundred and fifty!"
  | HighTon pts -> printfn "Wahoo! High ton of %d points!" pts
  | Ton80       -> printfn "Onnneeee Hundred and eightyyyyyy!!!!"
  | Invalid     -> printfn "Score must be between 0 and 180."

Summary

A single-case active pattern is good for creating a view of a complex or large model. We can define subsets of properties or values we require from the model which can be pattern matched. The single-case active pattern has a limited use case and isn't used very often. Multicase active patterns are good when your input can be categorized into a finite set of values. Multicase active patterns are used quite regularly in combination with other kinds of active patterns.

Improving an Old Pattern

I wrote a blog post called Some(“F#”) is better than None back in 2015. I’ve grown quite a bit as a developer since then. Growth is what we strive for in this industry. Looking back over the blog post, I would like to improve upon the code to make it more expressive and bug free. The code I am focused on is comparing assembly versions to indicate whether an update would be required. The original code looked similar to this:

let checkForUpdate (rMaj, rMin, rBld) userVer =
    match (uMaj, uMin, uBld) with
    | (x, _, _)       when x < rMaj -> "Major"
    | (rMaj, x, _)    when x < rMin -> "Minor"
    | (rMaj, rMin, x) when x < rBld -> "Build"
    | _                             -> "None” 

My first instinct of change would be to return a Discriminated Union instead of a string. Using a discriminated union would give me type safety as well as better pattern matching later on in in other functions. I would define the discriminated union as:

type UpdateType =
    | Major
    | Minor
    | Build
    | NoUpdate

The checkForUpdate function would then look like:

let checkForUpdate (rMaj, rMin, rBld) userVer =
    match (uMaj, uMin, uBld) with
    | (x, _, _)       when x < rMaj -> Major
    | (rMaj, x, _)    when x < rMin -> Minor
    | (rMaj, rMin, x) when x < rBld -> Build
    | _                             -> NoUpdate 

This is a minor change with big benefits. Other functions handling the result can now pattern match on the type safe discriminated union instead of the infinite variations of potential string values.

Expanding the guards

I decided to add some tests for the function. I used FsCheck (a property based testing library) to battle test the function. This turned out to be a useful exercise as an issue was identified. The patterns were not handling the case correctly for a user who has an alpha or beta version which is considered a higher version than the current release.

To resolve the issue I ended up having to expand the guard clauses. I made the mistake of shadowing the release version labels rMaj and rMin. I had thought they would match the value contained in the labels, similar to an erlang pattern match. I was wrong. Shadowing a label means the system creates a new label of the same name within the current/nested scope.

let checkForUpdate (rMaj, rMin, rBld) userVer = 
    match userVer with
    | (uMaj, _, _) 
        when rMaj > uMaj                                     
            -> Major
    | (uMaj, uMin, _) 
        when rMaj = uMaj && rMin > uMin                   
            -> Minor
    | (uMaj, uMin, uBld) 
        when rMaj = uMaj && rMin = uMin && rMin > uBld 
            -> Build
    | _     -> NoUpdate

After the guard expansions all my tests passed. Although it works, this pattern match isn’t as expressive as we may like. Going forward, we should look into using Active Patterns.

Summary

There are some things to be aware of when using pattern matching. We can’t compare bound values directly in the pattern, we have to use a guard clause to compare them. Attempting to match bound values in the pattern will not match and will create a shadowed value. Use discriminated unions (DU) when given the choice for patterns. DUs improve the type safety of our functions and limit the number of variations our functions have to account for. Passing strings into functions have an infinite number of variations and for the most part be avoided. Sometimes making some small changes can make for more robust functions.