Tuesday, April 22, 2008

F# Pattern Matching

After my last post on the sample F# test runner, I realized I missed a post on one of the critical constructs F# has to offer; pattern matching. Pattern matching does exactly what it sounds like, matches patterns.
let action x = 
     match x with
     | "Red" -> "Stop"
     | "Green" -> "Go"
     | _ -> "I don't know"
In this example, when I pass the colors Red or Green to the action function, it returns the matching value. If I were to pass blue to the function, I would get back "I don't know".
> action "Red";; val it : string = "Stop" > action "Blue";; val it : string = "I don't know"
Notice the "_" at the end, it is a wildcard and it makes this pattern match exhaustive. If I didn't include the _, F# would complain I had an incomplete match. You do have to be careful of where you put your wildcard character. The following function would create a scenario where the "Green" match statement would not be evaluated:
let action x = 
     match x with
     | "Red" -> "Stop"
     | _ -> "I don't know"
     | "Green"  -> "Go"
This control structure should seem very familiar to anyone coming from the C language family. Indeed, the action function could be written as an if..else statement or a switch statement in C#:
   public static string Action(string color)
        {
            if (color == "Red")
            {
                return "Stop";
            }
            else if( color == "Green")
            {
                return "Go";
            }
            else
            {
                return "I don't know";
            }
        }
You can include conditions within your matches, take the following function:
let isEven x =
     match x with
     | _ when x % 2 = 0 -> true
     | _ -> false
With conditions however, you need to have a catch all. If I were to change the above function to read like this:
let isEven x =
     match x with
     | _ when x % 2 = 0 -> true
     | _ when x % 3 = 0 -> false
the complier would complain of an incomplete pattern match. With pattern matching you can decompose structured values. This example breaks down the tuples for the match.
let isCubeEven x =
     match x with
     | _ when snd x % 2 = 0 -> (fst x,true)
     | _ -> (fst x,false)
This function returns the following in fsi:
> isCubeEven (3,9);; val it : int * bool = (3, false)
The example "isCubeEven" highlights two features of what makes F# exciting. First, notice that I didn't need to explicitly annotate any of my variables with types? The F# complier inferrs that x is a tuple because of the way I use it within my function. And second, the fst and snd functions that let me easily work with tuples to get the first and second values. Pattern matching becomes really powerful when you combine it with rec, the keyword for recursive functions. Take this function for adding up the numbers in a number:
let rec sumDigits x y =
    match x with
          | x when x = 0 ->  y
          | x -> sumDigits (x / 10) (x % 10) + y
This particular function I wrote to solve some of the Project Euler problems. With this function y accumulates the running summed value and x gets chopped up from right to left.
> sumDigits 123456789 0 val it : int = 45
When you combine the rec keyword and pattern matching, the possibilities are infinite. Take the fold_left function from the F# list namespace:
let rec fold_left f s l = 
     match l with 
     | [] -> s 
     | (h::t) -> fold_left f (f s h) t
The fold_left function takes a function f, a seed value s, and a list l. The match uses the list processing keys to get the first element h and remainder of the list t. Then calls itself with the same function, the result of the function applied to the seed value and the first element and the rest of the list. Pattern matching also works great on records, here's a great post on that by Mike Gold. Question and comments are welcome!

Friday, April 11, 2008

Sample F# Test Runner (and C# too...)

While I was fooling around with F# and testing my functions, I got really annoyed with switching between applications to run my tests, Visual Studio and NUnit Gui. The source of my fustration is during the day I get to use a fantastic tool TestDriven.Net to run my unit tests in C# and Visual Studio. With F#, however, TestDriven.Net does not recognize the different syntax and no tests run. So I decided to write my own test runner, by taking advantage of .Net's FileSystemWatcher class. (Note, I used the FileSystemWatcher.Changed event.1 ) The first iteration I wrote in C# so I could have an excuse to use xUnit. I probably over engineered the C# code, but old habits die hard. In the C# implementation, there are three files. The program class sets everything up, the HarnessRunner class sets up the filesystem watcher and listens for the file changed event, and finally the ProcessStarter to run the process. The application is configurable to use the app.config or to pass any necessary commands to the program. ( Hence a little of the bloat.)
using System;
using System.IO;

namespace HarnessRunner
{
    public class program
    {
        [STAThread]
        public static void Main(string[] args)
        {
            Settings settings = Settings.Default;

            if (ValidateInputValue(settings.TestRunnerCommand))
            {
                Console.WriteLine("Enter the fully qualified command to run:\r\n");
                settings.TestRunnerCommand = Console.ReadLine();
            }

            if (ValidateInputValue(settings.TestAssembly))
            {
                Console.WriteLine("Enter the file to test:\r\n");
                settings.TestAssembly = Console.ReadLine();
            }

            if (ValidateInputValue(settings.TestRunnerSwitches))
            {
                Console.WriteLine("Enter any swtiches to the test runner:\r\n");
                settings.TestRunnerSwitches = Console.ReadLine();
            }
                     
            Console.WriteLine("Setting up the watcher to run: \r\n{0} {1} {2}", Path.GetFileName(settings.TestRunnerCommand),
                              Path.GetFileName(settings.TestAssembly),settings.TestRunnerSwitches);
            try
            {
                HarnessRunner runner = new HarnessRunner(new FileSystemWatcher(), new ProcessStarter());
                runner.InitializeFileSystemWatcher(settings.TestAssembly);
            }
            catch (Exception e)
            {
                Console.WriteLine("There was an exception during the run: {0}{1}{2}", e.Message, Environment.NewLine,
                                  e.StackTrace);
            }
            Console.ReadLine();
        }

        private static bool ValidateInputValue(string command)
        {
            return string.Compare(command, string.Empty) == 0;
        }
    }
}
using System;
using System.Diagnostics;
using System.IO;

namespace HarnessRunner
{
    public interface IStartTestHarnesses
    {
        string Start();
    }

    public interface IFileSystemWatcher
    {
        bool EnableRaisingOfEvents { get; set; }
        string Path { get; set; }
        NotifyFilters NotifyFilter { get; set; }
        event EventHandler Changed;
    }

    public class ProcessStarter : IStartTestHarnesses
    {
        public string Start()
        {
            Settings settings = Settings.Default;
            Process process = new Process();
            process.StartInfo.FileName = settings.TestRunnerCommand;
            process.StartInfo.Arguments = settings.TestAssembly + " " + settings.TestRunnerSwitches;
            process.StartInfo.UseShellExecute = false;
            process.StartInfo.RedirectStandardOutput = true;
            process.Start();

            return process.StandardOutput.ReadToEnd();
        }
    }
}
using System;
using System.IO;

namespace HarnessRunner
{
    public class HarnessRunner
    {
        private FileSystemWatcher fileSystemWatcher;
        private IStartTestHarnesses startTestHarnesses;

        public HarnessRunner(FileSystemWatcher fileSystemWatcher, IStartTestHarnesses startTestHarnesses)
        {
            this.fileSystemWatcher = fileSystemWatcher;
            this.startTestHarnesses = startTestHarnesses;
            fileSystemWatcher.Changed += fileSystemWatcher_Changed;
        }

        private void fileSystemWatcher_Changed(object sender, FileSystemEventArgs e)
        {
            Console.WriteLine(startTestHarnesses.Start());
        }

        public void InitializeFileSystemWatcher(string filetowatch)
        {
            fileSystemWatcher.Path = Path.GetDirectoryName(filetowatch);
            fileSystemWatcher.Filter = Path.GetFileName(filetowatch);
            fileSystemWatcher.NotifyFilter = NotifyFilters.LastWrite;
            fileSystemWatcher.EnableRaisingEvents = true;
        }
    }
}

So, once I had that working, I decided to write the F# equivalent. There are two functions, one that starts the process and one to setup the filesystemwatcher. Other than that, there are just some config options.
#light

open System
open System.IO
open System.Diagnostics
open System.Configuration

let mutable command = ConfigurationManager.AppSettings.Item("TestRunnerCommand")
let mutable testAssembly = ConfigurationManager.AppSettings.Item("TestAssembly")
let mutable commandSwitches = ConfigurationManager.AppSettings.Item("TestRunnerSwitches")

let startProcess f =
     let p = new Process()
     p.StartInfo.FileName <- command
     p.StartInfo.Arguments <- f ^" "^commandSwitches
     p.StartInfo.UseShellExecute <- false
     p.StartInfo.RedirectStandardOutput <- true
     let started = p.Start()
     printfn "%O" (p.StandardOutput.ReadToEnd())

let SetupFileSystemWatcher f =
     let fileSystemWatcher = new FileSystemWatcher()
     fileSystemWatcher.Path <- System.Environment.CurrentDirectory
     fileSystemWatcher.Filter <- f
     fileSystemWatcher.NotifyFilter <- NotifyFilters.LastWrite
     fileSystemWatcher.EnableRaisingEvents <- true
     fileSystemWatcher.Changed.Add(fun _ -> startProcess f)
   
if command = ""  command = null then
     printfn "Enter the fully qualified command to run:\r\n"
     command <- Console.ReadLine()       
if testAssembly = ""  testAssembly = null then      
     printfn "Enter the file to test:\r\n"
     testAssembly <- Console.ReadLine()
if commandSwitches = ""  commandSwitches = null then
      printfn "Enter any swtiches to the test runner:\r\n"
      commandSwitches <- Console.ReadLine()

printfn "Setting up watcher for %A" testAssembly  
SetupFileSystemWatcher testAssembly 
read_line() 
So, now that the code is posted, I'm left with some sort of follow up point to wrap this post up. The only problem is, I can't seem to come up with any points! I put together this article to show a program that performs the same function, implemented in two separate languages. Comments, feedback and discussions are welcome! 1 For some reason, the FileSystemWatcher.Changed event fires three times when my assembly is compiled.