United Kingdom: +44 (0)208 088 8978

Package references in F# 5 scripts

In F# 5, bringing external dependencies into FSI just got much easier

The problem: using a NuGet package in an F# script

Have you ever wanted to use a NuGet package from an F# script or in F# Interactive (FSI)? Using external packages can obviously greatly enhance what you can do in a script file. Also, you can quickly explore the features of a library.

What did we do before F# 5?

Previously, one solution to this problem was to download the DLLs you want to reference and use #r to point to a DLL file.

#r @"./packages/farmer/1.3.2/lib/netstandard2.0/Farmer.dll"

One major problem with this is that you need to make sure you also include references to any other DLLs that this DLL depends on, with compatible versions. This is why we have package managers.

This is where Paket came in with a better option. It has a generate load scripts feature, where you can reference packages and package groups that you have defined in your paket.dependencies file.

What we can do now

One of the new features added in F# 5 is package references for FSI. You can use #r to reference a NuGet package directly like so: #r "nuget: PackageName". Here is a fuller example using the Farmer library for declaratively deploying Azure resources.

#r "nuget: Farmer,1.3.2" // Includes an optional version number

open Farmer
open Farmer.Builders

let deployment = arm {
    add_resource (storageAccount { name "myteststorage" })
}

deployment.Template |> Writer.toJson
(* Returns:
{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "outputs": {},
  "parameters": {},
  "resources": [
    { "apiVersion": "2019-06-01",
      "dependsOn": [],
      "kind": "StorageV2",
      "location": "westeurope",
      "name": "myteststorage",
      "properties": {},
      "sku": {
        "name": "Standard_LRS"
      },
      "tags": {},
      "type": "Microsoft.Storage/storageAccounts"
    }
  ]
}
*)

Note: if you're writing a script for re-use, it's better to explicitly include a version number. If you're just exploring the library in FSI then this isn't needed, and the latest version of the package will be used.

Note: that if you have multiple consecutive package references, it will resolve the packages together, ensuring that all transitive dependencies have consistent versions.

Limitations

I should mention some limitations of using this new feature to create a re-usable script.

  • If you need a script that will load exactly the same references as those used by a project you're working on, then these package references probably won't work very well for you. In that case you should use Paket as a dependency manager and use the "generate load scripts" feature described above.

  • There is no lock file. Even if you specify package versions, you can't be sure that the transient dependencies will stay the same if you run the same script at a later date.