Mark Needham

Thoughts on Software Development

F#: Refactoring to pattern matching

with 12 comments

I was looking through some of the F# code I’ve written recently and I realised that I was very much writing C# in F# with respect to the number of if statements I’ve been using.

I thought it would be interesting to see what the code would look like if I was able to refactor some of that code to make use of pattern matching instead which would be a more idiomatic way of solving the problem in F#.

The first example of if statements is in my post about my F# solution to Roy Osherove’s TDD Kata.

I originally wrote a parse function which was able to parse a string and give it’s decimal value or 0 if it couldn’t be parsed.

let parse value = 
    let (itParsed, value) = Decimal.TryParse value
    if (itParsed) then value else 0.0m

If we use a pattern match expression we’d end up with the following:

let parse value = match Decimal.TryParse value with | (true, value) -> value | (false, _) -> 0.0m

The neat thing about this approach is that we don’t need to store the result of the ‘Decimal.TryParse’ function as we did in my original version.

We could in theory also write it like this…

let parse value = match Decimal.TryParse value with | (true, value) -> value | (_, _) -> 0.0m

…but while that is slightly less code I quite like the other version because it’s a bit more intention revealing that 0 will be returned if we fail to parse the string. I think there’s less thinking needed to understand the code.

Another example is this bit of code:

let add value = if ("".Equals(value) or "\n".Equals(value)) then 0.0m
                else match digits value |> Array.filter (fun x -> x < 1000m) with 
                     | ContainsNegatives(negatives) -> raise (ArgumentException (buildExceptionMessage negatives))
                     | NoNegatives(digits)          -> digits |> Array.sum

If we convert that to use pattern matching we would get this:

let add value = match value with
                | "" -> 0.0m
                | "\n" -> 0.0m
                | value ->  match digits value |> Array.filter (fun x -> x < 1000m) with 
                            | ContainsNegatives(negatives) -> raise (ArgumentException (buildExceptionMessage negatives))
                            | NoNegatives(digits)          -> digits |> Array.sum

That’s maybe slightly easier to read mainly because of the splitting up of the two inputs which lead to a 0 result.

The final example of using if statements is the following bit of code:

let (|CustomDelimeter|NoCustomDelimeter|) (value:string) = 
	...
 
     if (value.Length > 2 && "//".Equals(value.Substring(0, 2))) then
         if ("[".Equals(value.Substring(2,1))) then CustomDelimeter(delimeters value)
         else CustomDelimeter([| value.Substring(2, value.IndexOf("\n") - 2) |])
     else NoCustomDelimeter(",")

The only way I could see how to make this use active patterns is on the boolean statements like so:

let (|CustomDelimeter|NoCustomDelimeter|) (value:string) = 
	...
 
    match (value.Length > 2 && "//".Equals(value.Substring(0, 2))) with
    | true -> match ("[".Equals(value.Substring(2,1))) with 
              | true  ->  CustomDelimeter(delimeters value)
              | false ->  CustomDelimeter([| value.Substring(2, value.IndexOf("\n") - 2) |])
    | false -> NoCustomDelimeter(",")

I quite like that it lines up the return values of the active pattern which seems to make it a bit more readable.

I think overall maybe the pattern matching versions are slightly more readable but maybe not by much. What do you think?

Be Sociable, Share!

Written by Mark Needham

January 12th, 2010 at 1:33 am

Posted in F#

Tagged with

  • OJ

    I much prefer the pattern-matching examples. They’re definitely more readable and “feel” more functional (lame justification I know). I find that it’s way easier to see the flow of data through your functions when using the pattern matching features.

    I think you could go further with your matching to reduce nesting of matches too (using tuples).

    Nice work though 🙂
    OJ

    PS. Do you know of a way to get F# to recognise strings as char lists without having to explicitly call ToCharArray()?

  • Hey,

    So I’m assuming you mean something like this:

    match (value.Length > 2 && “//”.Equals(value.Substring(0, 2))), (“[“.Equals(value.Substring(2,1))) with
    | false, _ -> NoCustomDelimeter(“,”)
    | true, true -> CustomDelimeter(delimeters value)
    | true, false -> CustomDelimeter([| value.Substring(2, value.IndexOf(“\n”) – 2) |])

    The problem is the second item in the tuple fails if the first one return false because the string isn’t necessarily long enough for it to execute correctly.

    Is there a way to stop that from happening to allow the expression to be written as a tuple?

    Playing around with some code if you do a for expression it seems to treat the string as an array

    for char in “myString”
    // here char is ‘Char’

  • Matt

    The first example is vastly superior in communicating intent.

    The others are still better than the originals, especially since the indentation seems much more in sync with what I expect of f# code.

    I really rather like the

    match expression with
    | true -> what to do
    | false -> what to do

    rather than
    if (expression) then
    what to do
    else
    what to do

    and the compact version of the if then else is nasty due to lack of alignment (especially with longer guard expressions)

    rather than
    if (expression) then what to do
    else what to do

    If you have a *really* trivial one liner, say 50 characters or less of the form

    if (x) then y else z

    then that wins for me over

    match x with | true -> y | false -> z

    but the moment you need to put in a temporary let binding to reference something (as in your first example) that match lets you avoid it wins.

    Essentially if then else is most useful for me as the equivalent of the ternary operator (but with expression goodness)

  • Pingback: The Morning Brew - Chris Alcock » The Morning Brew #515()

  • Hi,

    Tests such as : “\n”.Equals(value) seem odd to me. I would simply write value = “\n”.

    Also, your code:
    let add value = match value with
    | “” -> 0.0m
    | “\n” -> 0.0m

    could be written:
    let add = function
    | “” | “\n” -> 0.0m

    It looks better to me.

  • Excellent idea to post about this. I think a lot of C# programmers like me are still struggling to give these ‘obvious’ features a front row seat in their brain.

  • @Laurent – ahhhhh I just realised what I was doing wrong with that “\n”.Equals(…) code.

    I originally wrote it as ‘”\n” == …’ which of course doesn’t work because you need to use = and not ==! So that’s why I changed to use the Equals method instead. My bad!

    @Matt – I like your summary of when we could use each of the options. That makes sense.

  • Ron Lewis

    I think you can ALWAYS rewrite an if then expression as a match expression.

    if boolean_expression then
    do_something
    else
    do_nothing

    …can be rewritten as:

    match boolean_expression with
    | true -> do_something
    | false -> do_nothing

    a variation:

    match boolean_expression with
    | true -> do_something
    | _ -> do_nothing

    And if we are really doing nothing for false:

    match boolean_expression with
    | true -> do_something
    | _ -> ()

    Therefore, whenever you see an if then expression in F#, you can refactor it into a match expression.

    I like the match better than the if then. I think the match is truly more idiomatic of F#. Why use F# to write C# code? Use C# for C# thinking. Use F# for F# thinking.

    I liked the article very much.

  • Ron Lewis

    This site does not preserve leading spaces on a line. So, if
    ` I post another comment. I think I will try something
    ` like this (if it works). … Because indentation is sometimes
    ` important. For example, in F# code, indentation is very
    ` important.

  • Pingback: Rick Minerich's Development Wonderland : F# Discoveries This Week 01/13/2010()

  • lloyd

    let parse value = match Decimal.TryParse value with | (false, _) -> 0.0m | (_, value) -> value

  • Most of your brackets are superfluous. This isn’t Lisp!