F# Scripts

Exploring running F# from a fsx script file


F# FSX

Using F# scripts is something I only started doing after dabbling in F# for quite a while. This is unfortunate because they are a really fast and easy way of throwing some code together and thus a really good way to learn F#. This post is for anyone getting started with F# scripting.

Installation

Check out the documentation for installing F#. The easiest way is to install F# as part of Visual Studio. You may still need to add FSI.exe to your PATH.

  1. Instructions for Windows
  2. Instructions for MacOS
  3. Instructions for Linux

F# Interactive

FSI allows you to execute F# code in an interactive console. Just type fsi.exe on Windows or fsharpi on Linux/Mac.

> let x = 1;;
val x : int = 1

Note: Each expression needs to end with ;; in the interactive window.

To get help: #help;;
To quit: #quit;;

Scripting

So entering code directly into fsi is ok for trying simple things out but what about more complex code. That is where .fsx files come in.

Imagine we have a file called print-name.fsx with the following content:

let name = "Devon"
printfn "Name: %s" name

Executing it we would see the following:

> fsi .\samples\print-name.fsx
> Name: Devon

Including other fsx files

You can load other fsx files into a script. If we have Strings.fsx containing the following code:

let toUpper (s:string) = s.ToUpper()
let toLower (s:string) = s.ToLower()
let replace (oldValue:string) (newValue:string) (s:string) = s.Replace(oldValue,newValue)

module StringBuilder =
    open System.Text
    let init() = new StringBuilder()
    let initWith(s:string) = new StringBuilder(s)
    let append (s:string) (sb:StringBuilder) = sb.Append(s)

We can now use it in our script file like so:

#load "Strings.fsx"
open Strings
let name = "Devon" |> StringBuilder.initWith
            |> StringBuilder.append " Burriss"
            |> string |> toUpper
printfn "Name: %s" name

We use #load "path/to/script.fsx" to make it available and then open NameOfFileWithoutExtension to import it. So each script file is then treated as a module.

Executing it we would see the following:

> fsi .\samples\print-name.fsx
> Name: DEVON BURRISS

Taking Arguments

It is possible to pass arguments into a script file. They are available in a field fsi.CommandLineArgs. Let's change our script one more time to demonstrate the usage. The arguments come through as an array so we pattern match on the number of elements to decide what to print.

Note: The first element of the array is always the name of the script the arguments are passed into

#load "Strings.fsx"
open Strings
let stringWithSpace x = x |> string |> (sprintf " %s")
let name first = first |> toUpper
let nameAndLastName first last = first |> StringBuilder.initWith |> StringBuilder.append last |> stringWithSpace |> toUpper
let nameAndLastNameWithOccupation first last occ = 
    first |> StringBuilder.initWith 
    |> StringBuilder.append " " 
    |> StringBuilder.append last 
    |> StringBuilder.append (sprintf " (%s)" occ)
    |> string |> toUpper

match fsi.CommandLineArgs with
| [|scriptName;|] -> failwith (sprintf "At least a name required for %s" scriptName)
| [|_;firstName|] -> name firstName |> printfn "Name: %s"
| [|_;firstName; lastName|] -> nameAndLastName firstName lastName |> printfn "Name: %s"
| [|_;firstName; lastName; occ|] -> nameAndLastNameWithOccupation firstName lastName occ |> printfn "Name: %s"
| _ -> failwith (sprintf "Too many arguments %A" (fsi.CommandLineArgs |> Array.tail))

Executing it we would see the following:

> fsi .\samples\print-name.fsx devon burriss developer
> Name: DEVON BURRISS (DEVELOPER)

You could of course also access the arguments as a zero based array fsi.CommandLineArgs.[0] or loop through them.

Referencing DLLs

You can reference DLLs using #r "path/to/file.dll". If you want to pull down DLLs from Nuget, check out my article on using Paket dependency manager.

Resources

  1. FSI Documentation
  2. 10 Tips for Productive F# Scripting

Credits

  1. Social Image by yifei chen



blog comments powered by Disqus