F# Active Patterns are awesome!! I wanted to start this blog post with that statement. I was not truly aware of F# active patterns until I read the article on F Sharp for Fun and Profit when I was looking at a better way to use the .Net Int32.Parse function.
Active patterns are a function that you can define that can match on an expression. For example:
let (|Integer|_|) (s:string) = match Int32.TryParse(s) with | (true, i) -> Some i | _ -> None
To explain what each line is doing the | characters are called banana clips (not sure why) and here we are defining a partial active pattern. This means the pattern has to return Option of some type. So be definition the pattern may return a value or it may not so it is a partial match. This pattern takes a string and then returns Some int if the pattern matches or None if it does not. Once this pattern is defined it can be used in a normal match expression as follows:
let printNumber (str:string) = match str with | Integer i -> printfn "Integer: %d" i | _ -> "%s is not a number" %s
Using this technique it allows us to define a really cool active pattern for matching regular expressions and parsing out the groups that are matched:
let (|ParseRegex|_|) regex str = let m = Regex(regex).Match(str) match m with | m when m.Success -> Some (List.tail [ for x in m.Groups -> x.Value] ) | _ -> None
This regex active pattern returns all of the matched groups if the regex is a match or None if it is not a match. Note the reason List.tail is used is to skip the first element as that is the fully matched string which we don’t want, we only want the groups.
The reason why all of this came up is that the getNextAvailableName function that I wrote about in my last blog post is very long winded. For those who haven’t read my last post this function generates a unique name by taking a candidate name and a list of names that have been taken. If the name passed in has been taken then the function will add a number on to the end of the name and keep incrementing it until it finds a name that is not taken. The getNextAvailableName function was defined as:
let rec getNextAvailableName (name:string) (names: string list) = let getNumber (chr:char) = match Int32.TryParse(chr.ToString()) with | (true, i) -> Some i | _ -> None let grabLastChar (str:string) = str.[str.Length-1] let pruneLastChar (str:string) = str.Substring(0, str.Length - 1) let pruneNumber (str:string) i = str.Substring(0, str.Length - i.ToString().Length) let getNumberFromEndOfString (s:string) = let rec getNumberFromEndOfStringInner (s1:string) (n: int option) = match s1 |> String.IsNullOrWhiteSpace with | true -> n | false -> match s1 |> grabLastChar |> getNumber with | None -> n | Some m -> let newS = s1 |> pruneLastChar match n with | Some n1 -> let newN = m.ToString() + n1.ToString() |> Convert.ToInt32 |> Some getNumberFromEndOfStringInner newS newN | None -> getNumberFromEndOfStringInner newS (Some m) let num = getNumberFromEndOfStringInner s None match num with | Some num' -> (s |> pruneNumber <| num', num) | None -> (s, num) let result = names |> List.tryFind(fun x -> x = name) match result with | Some r -> let (n, r) = getNumberFromEndOfString name match r with | Some r' -> getNextAvailableName (n + (r'+1).ToString()) names | None -> getNextAvailableName (n + "2") names | None -> name
With the Integer and Regex active patterns defined as explained above the new version of the getNextAvailableName function is:
let rec getNextAvailableName (name:string) (names: string list) = let result = names |> List.tryFind(fun x -> x = name) match result with | Some r -> let (n, r) = match name with | ParseRegex "(.*)(\d+)$" [s; Integer i] -> (s, Some i) | _ -> (name, None) match r with | Some r' -> getNextAvailableName (n + (r'+1).ToString()) names | None -> getNextAvailableName (n + "2") names | None -> name
I think it is pretty incredible how much simpler this version of the function is. It does exactly the same job (all of my tests still pass:) ). It really shows the power of active patterns and how they simplify your code. I think they also make the code more readable as even if you didn’t know the definition of the ParseRegex active pattern you could guess from the code.
Check out the full source code at SqlJuxt GitHub repository.