Scripting in F#

The thing you hear most about F# is that it is multi-paradigm, meaning that you can use it to code in multiple styles of programming. But F# spans multiple-domains too. F# is not only well suited for quantitative computing, but it is surprisingly well suited for scripting as well.

The Desktop Scripting Scenario

The use-case for scripting is that you want to automate some task but don’t feel like resorting to writing a full-blown application to do it for you. For example, on the test team we use Visual Studio Team Test edition to gather Code Coverage for the F# product. In order to get our bits ready for code coverage I need to do the following:

  • Un-GAC the assembly
  • Instrument it using a filter
  • Disable strong name verification
  • GAC it
  • Delete NGEN images
  • NGEN it
  • [Run tests]
  • Un-GAC the instrumented binary
  • Delete NGEN images

Those steps are tedious as-is, let alone when you want to repeat the process for 10 different binaries!

Now I could write some wizard-style application to manage the general task of instrumenting assemblies and setting up the machine, but now I need to worry about source control, versioning, and installing this new app. Pffft whatevs.

Instead to solve this problem I’ve written an F# Script File (.fsx), which is an entire F# Project rolled into one file.

Note: Before you get excited and remind me that Windows PowerShell exists, I’ll just say the only thing better than learning a new programming language AND a new shell language, is just learning a new programming language.

So what exactly is an F# script file?

An F# script file is a normal F# source code file, except that .fsx files have a few extra capabilities.

Windows Explorer Integration

Double-Clicking on a .fsx file opens it up in Visual Studio. This gives you intellisense for editing. And the ability to highlight some code and run in F# Interactive for Visual Studio.

Right-Clicking on it has the context menu in Explorer shows an ‘Run with F# Interactive’ action which will execute the script directly through FSI.

clip_image002

Extra perks of F# Scripts

F# script files also have extra features available to them to compensate for not having project support. Again, think of .FSX files as a single-file project.

Adding Reference Via Code

When editing an F# Project in Visual Studio, to reference a separate assembly you can add a project reference through the UI. In F# scripts however you can just type #r and load an assembly. By default it will pick things out of the current directory and/or search paths (see below). (This was always allowed in previous releases of F#, but in the CTP this is only available to .fsx script files.)

 #r "System.Core.dll"

open System.Collections.Generic

let hs = new HashSet<int>()

Add Directories to the Search Path

#r Will get things out of the GAC, but fully qualifying reference paths is a pain. You can use #I however to add a ‘search path’ to indicate where to look for references.

 #I @"C:\Program Files (x86)\Reference Assemblies\Microsoft\VSTO\v9.0"

#r "Microsoft.Office.Tools.Common.v9.0.dll"

#r "Microsoft.Office.Tools.Excel.v9.0.dll"

Load Other Source Files

When I said F# Script files were one-file projects, I slightly lied. By using #load you can load additional source files into the script. This way you can reuse library-type code. Note that this is not recommended as a way for breaking up large scripts. If your script is too large for one file, you should consider creating an actual F# project.

 #load "HelperLibrary.fs"

let x = HelperLibrary.SomethingAwesome()

Script Snippets

For building your own F# scripts, here are some simple building blocks to start from:

 /// Get all files under a given folder
open System.IO

let rec allFilesUnder baseFolder = 
    seq {
        yield! Directory.GetFiles(baseFolder)
        for subDir in Directory.GetDirectories(baseFolder) do
            yield! allFilesUnder subDir 
        }
    
/// Active Pattern for determining file extension
let (|EndsWith|_|) extension (file : string) = 
    if file.EndsWith(extension) 
    then Some() 
    else None

/// Shell executing a program
open System.Diagnostics

let shellExecute program args =
    let startInfo = new ProcessStartInfo()
    startInfo.FileName <- program
    startInfo.Arguments <- args
    startInfo.UseShellExecute <- true

    let proc = Process.Start(startInfo)
    proc.WaitForExit()
    ()

Example F# Script

Putting the code above together yields a script for opening up each .fs or .fsi file under the script folder in Notepad. Not bad for six lines of code, and with some work you could even clean it up further taking advantage of Language Oriented Programming techniques…

 // Launches all .fs and .fsi files under the current folder in Notepad
open System

allFilesUnder Environment.CurrentDirectory
|> Seq.filter (function 
            | EndsWith ".fs" _
            | EndsWith ".fsi" _
                -> true
            | _ -> false)
|> Seq.iter (shellExecute "Notepad.exe")

 

I’ve found F# scripts to be useful in my work. I’d love to hear from you about what sorts of applications you use FSX files for.