Adventures in F#–Sweet Test-First Kung Fu

Jomo Fisher--Up until now, I've been avoiding using F# with the VS IDE. I've been using notepad.exe and fsc.exe because I wanted to build my own expectation for what the experience should be before I experienced what it actually was. I can tell you that I didn't expect the sweet experience of using the F# interactive window.

I've written in the past about Test Driven Development. Regardless of language, I really like the process of iteratively writing code and unittests tests for that code. For very small projects--simple algorithms and classes--I've found the process of getting going very tedious compared to the amount of actual code I want to write. In order to get started, you need a new project and solution for your code, a separate project for your tests and you need to reference your code project from your test project. All this means friction and tends to discourage firing up VS and freely inventing for very small projects.

Enter the F# Interactive window. Once you've installed F# and checked the Add-in box under Tools\Add-in Manager you'll see the F# interactive window. You can type code into this window and it will execute immediately. Importantly, you can select some text from your current source file and press alt-enter to execute it.

So here's my new process for these tiny one-off projects:

1) Ctrl-N to create a new text file (rename to mycode.fs)

2) Type in the skeleton of the function

3) Select function and press alt-enter

4) Write test, select it and press alt-enter (see failure)

5) Fix function to make test pass and goto 3

Notice that there's not a project or even a solution involved here--its just developer writing code. It's a really sweet experience. I only wish F# would install over C# Express 2008 since that's what I tend to use at home.

I like to show code in my posts when possible and I want to give you a visual idea of what I'm talking about. Here's a simple function I wrote--with tests--that takes a string in the form of A.B.C.D or vA.B.C.D and returns a tuple of 16-bit ints with values (A,B,C,D). The tests are at the end.

let ParseVersion s = 
    // Build a list with each element of the version in reverse order.
    let rec parse(s:string,pos,count,result) = 
        let versionSize = 4
        if pos >= 0 then 
            if s.[pos] = 'v' && pos = 0 then parse(s,1,0,[])
                let dotPos = s.IndexOf(".", pos)
                if dotPos = -1 then parse(s, -1, count+1, int_of_string(s.Substring(pos))::result)
                else parse(s, dotPos+1, count+1, int_of_string(s.Substring(pos, dotPos-pos))::result)
        elif count < versionSize then parse(s, pos, count+1, 0::result) 
        else result
    let l = parse(s,0,0,[])
    (uint16(List.nth l 3), uint16(List.nth l 2), uint16(List.nth l 1), uint16(List.nth l 0))
ParseVersion "2.0.1"
ParseVersion "2.0"
ParseVersion "v2.0"
ParseVersion "v1.2.3.4"
ParseVersion "v1"
ParseVersion ""

(Notice how I made it tail-recursive to take advantage of F#'s tail-call optimization?)

I wrote this using more-or-less test-first principles and I was very happy with the flow of the process.

This posting is provided "AS IS" with no warranties, and confers no rights.

Comments (4)

  1. - says:

    I do this all the time in python, interactive windows are completely indispensable, and I miss them with other languages where I end up doing similar work by stepping through code in the debugger, doing the tedious edit-compile-run-to-the-code-I’m-testing loop. (No edit&continue on this platform).

    I’ll write everything in the interactive window, tweaking individual functions / lines of code until I’m happy with it and I’ve tested it for some throwaway cases, then when it seems good enough to start writing the next big chunk of copy, I copy-paste all the code into a file.

    Trying 3 or 4 ways of writing a line of code in quick succession is a great way to learn a language, and a great way to write and test code.

  2. Tony Nassar says:

    That’s a very good point: having a REPL encourages you to give in to the temptation to improve the line of code you just wrote. Trying to write F#, I’m shocked at how badly Visual Studio has spoiled me. ReSharper and Ankh have completely changed my workflow, in many ways for the better (I don’t hesitate to rename methods, for example), but there are things I used to do for myself in Emacs oh, 8 years ago, that I can’t do anymore.

  3. Daniel Abramov says:

    Hi Jomo! I enjoy your blog a lot, being a C# 3.0 and LINQ fan.

    I just started having fun with F#, and your blog entries about its insides shed some light on it for me.

    However I find this snippet of your code misusing (or probably overusing) some functional concepts such as tail recursion or list pattern matching. I decided to rewrite it in a more eye-friendly way because this kind of thought purity is why I love F# for. I'm not sure if it is exactly as correct as yours but it looks sexier to me:

    open System

    let parseVersion (s:string) =

       s.Split [|'v'; '.'|]

       |> List.ofArray

       |> List.filter ((<>) "")

       |> (Int32.Parse)

    let versions = [""; "v0.9"; "1.2.9"]

    let versions = strings |> parseVersion

    List.iter2 (printfn "String '%s' has been parsed as %A") strings versions                                        

Skip to main content