Embedding F# Compiler: Fsc-Host NuGet

I have created a NuGet package that lets you easily use F# scripting in your apps.

In two previous posts (1, 2) I showed you how to host the F# compiler in your own applications. This time I wanted to reveal the result of those experiments. I wanted to enable F# scripting in a few of my apps. This requirement made me create the Fsc Host project. The goal was to create a light-weight reusable abstraction over FSharp.Compiler.Service focused on compiling scripts and consuming them by the hosting application. The NuGet package is available here. The project is still in the experimental phase (version 0) so the API and available features may change, (and the changes may be breaking so bear that in mind before using it in your projects).

Fsc-Host requires the .NET SDK so the easiest way to use it is to distribute your application as a container image. This way you can ensure it always works as expected. If you reference NuGet packages in your script it will need connectivity to a NuGet source (whether it is nuget.org or your private NuGet feed). To use Fsc-Host you just need to reference the Queil.FSharp.FscHost package and write a few lines of code.

The source code for this example is available here.

let helloFromScript name = sprintf "HELLO FROM THE SCRIPT, %s" name // access it in the host app by: 
                         // Property<string -> string>.Path "Script.helloFromScript"

let myPrimes = [2; 3; 5] // access it in the host app by: 
                         // Property<int list>.Path "Script.myPrimes"
let (export, myPrimes) =
  File "script.fsx"
  |> CompilerHost.getMember2 Options.Default
        (Member<string -> string>.Path "Script.helloFromScript")
        (Member<int list>.Path "Script.myPrimes")
  |> Async.RunSynchronously

There are a few things that need to be considered:

  • Default module name - if the script doesn’t define module or namespace, all the let bindings will be in the default top level module. The name of the module is usually a PascalCase version of the script file name (bar the extension). Also if the file name contains special characters it might require some debugging to figure out what it gets transformed to). So script.fsx becomes Script.

  • Consuming functions - it requires an additional step which is to convert a function to a function value. Function values can be easily downcast to F# functions in the hosting application. Hence the binding of helloFromScript to export. Now it is supported as is.

Wrapping up ¶

I hope you now have a high-level understanding of what I tried to achieve in the project. I hope some documentation will follow shortly. Until it happens feel free to browse through the tests to get an idea of what the exposed features are.