10 Tips for Productive F# Scripting

06 Feb 2016

Scott Hanselman recently had a nice post on C# and F# REPLs, which reminded me of the time I started using F# scripts. Over time, I found out a couple of small tricks, which helped make the experience productive. I found about them mainly by accident, so I figured, let’s see if I can list them in one place! Some of these are super simple, some probably a bit obscure, but hopefully, one of them at least will make your path towards scripting nirvana an easier one…

Note: these tips are not necessarily ordered by usefulness. For that matter, there might or might not be exactly 10 of them :)

Tip 1: Use .fsx Files for Interactive Coding

You can use the F# Interactive 2 ways: you can directly type code into FSI, the F# Interactive window, or you can write code in an .fsx file, and select pieces of the code you want to execute. I recommend the second approach, for at least two reasons. First, FSI is a very primitive environment, .fsx files provide a much richer experience (IntelliSense). Then this encourages writing clean scripts you can reuse later.

This is not specific to scripts, but… if you are on Visual Studio, do yourself a service and install the Visual F# Power Tools - you’ll get nice things such as better code highlighting, refactoring, and more.

To execute code interactively, simply type code in an .fsx file, select a block of code, and hit Alt + Enter. The selected code will be evaluated, and the result will show up in the FSI window. In Visual Studio, you can also select code and right-click “Execute in Interactive”, but shortcuts are way faster.

You can also execute a single-line with Alt + '. I rarely use this option, but this can save you time because you don’t need to select the entire line of code.

In case the keyboard shortcuts to send code to FSI do not work anymore (ReSharper used to over-write them in the past), you can reset them in Visual Studio, by going to Tools / Options / Environment / Keyboard. The 2 commands you need to map are EditorContextMenus.CodeWindow.ExecuteInInteractive and EditorContextMenus.CodeWindow.ExecuteLineInInteractive.

You can also use these shortcuts from a regular .fs file, which can be handy if you want to validate that a piece of code is behaving the way you want.

Interactive coding is by far my main usage for scripts - I use it extensively to prototype designs, run dumb tasks, or explore data or libraries. I realized recently that a few of my C# friends use LinqPad for the same purpose.

Tip 2: What is it?

While I encourage working primarily from .fsx files, the FSI window is also very helpful. I use it primarily for small verifications. For instance, I might have in my script file code like this:

letaddxy=x+y

Once I send it for evaluation into FSI, I will see the following show up in FSI:

valadd:x:int->y:int->int>

My function add is now in memory, in my FSI session; I can start typing in the FSI window and use it:

>add12;;valit:int=3>

Enter does not trigger execution in FSI. The ;; indicates to FSI “Please execute everything I just typed, up to that point”. This is useful if you want to type multiple lines of code in FSI, and execute them as a block.

it: in our add 1 2 example, the result showed up as it. We simply ran add, but didn’t assign the result to anything. it now contains the result, until we run another expression. If you want to re-use that value, you can assign it in FSI, by doing for instance let x = it;;.

Once a value is loaded in your FSI session, it will remain there, available to you until you shadow it (in the example above, x will remain available, until I run for instance let x = 42;;). This is extremely convenient: for instance, you can load a data file once let data = File.ReadAllLines path, and keep using data for as long as you want, without having to reload it between code changes.

What if you inadvertently started a very long computation, or an infinite loop? In Visual Studio, you can either kill the session entirely, by right-clicking over the FSI window and selecting “Reset Interactive Session” or Ctrl + Alt + R, or cancel the latest evaluation you requested (“Cancel Interactive Evaluation”, or Ctrl + Break.).

Tip 3: Run Scripts from the Command Line

Besides interactive scripting, you can also run a script from the command line, by using FSI.exe:

>fsi.exe "C:\myscript.fsx"

FSI.exe is typically located at C:\Program Files (x86)\Microsoft SDKs\F#\4.0\Framework\v4.0. You can also install it separately, see fsharp.org/use section for instructions for various platforms.

You can define different behaviors in your script, depending on whether it is run interactively or from the command line, like this:

Tip 5: Including Assemblies

By default, FSI loads FSharp.Core and nothing else. If you want to use System.DateTime, you will need to first open System in your script. If you want to use an assembly that is not part of the standard .NET distribution, you will need to reference it first using #r. Imagine for instance that you installed the Nuget package fsharp.data; to use it in your script, you would do something like:

Tip 6: Use Paket

The Nuget package manager is useful to consume existing packages. However, by default, Nuget stores assemblies in a folder that includes the package version number. This is very impractical for a script. In our example above, if fsharp.data gets an update, our script reference will be broken once we update the Nuget package:

#r @"../packages/FSharp.Data.2.2.5/lib/net40/FSharp.Data.dll"

Fixing the script requires manually editing the version number in the path, which quickly becomes a pain. Paket provides a better experience, because it stores packages without the version number, in this case, under:

#r @"../packages/FSharp.Data/lib/net40/FSharp.Data.dll"

Your scripts will now gracefully handle version number changes.

If you end up consuming numerous packages, you can make your life even easier, by referencing paths where assemblies might be searched for, using #I:

Tip 7: Include Files

You might want to use the code from an existing file in your script. Suppose that we have a code file Code.fs somewhere, looking like this:

namespaceMathiasmoduleCommon=lethelloname=sprintf"Hello, %s"name

You can use that code from your script, by using the #load directive:

#load"Code.fs"openMathias.Commonhello"World"

You might have to close and re-open the script file if you end up changing the contents of the file.

If the file you are attempting to load contains references to other assemblies or files, you might get an error on the #load statement: “One or more errors in loaded files. The namespace or module … is not defined”. Simply reference the missing assemblies above the #load statement, so that your script uses the same dependencies as the file it refers to.

Tip 8: Profile your Code with #time

Another handy directive, #time, turns on basic profiling. Once it is executed, for every block of code you send for execution you will see timing and garbage collection information. For instance, running this code…

We get the wall time and CPU time it took, as well as some information about garbage collection in generations 0, 1 and 2. This would not replace a full-blown profiler, but this is an awfully convenient tool to figure out quickly if there are obvious ways to improve a piece of code.

Note that every time you execute #time, the timer will be switched from on to off, or vice-versa. This is not always convenient; you can also explicitly set it to the desired state, like this: