How to Setup a minimal F# Project
Tags: fsharp
Published: 2020-07-14 23:21:51

In a previous [post](https://dev.to/ducaale/getting-started-with-f-the-easy-way-53h6), I highlighted how to use F# without setting up a .NET project. This time, we will look into the process of setting up a minimal F# project, adding unit-tests, and finally publishing it.

Let's start by scaffolding an F# project using `dotnet new` command

```bash
dotnet new console -lang "F#" -o hello-world
```

You should see a new folder named `hello-world` in the directory where you just ran the command
```
hello-world
├── obj
├── hello-world.fsproj
└── Program.fs
```

let's take a quick look at `Program.fs`
```fsharp
// Learn more about F# at http://docs.microsoft.com/dotnet/fsharp

open System

// Define a function to construct a message to print
let from whom =
    sprintf "from %s" whom

[<EntryPoint>]
let main argv =
    let message = from "F#" // Call the function
    printfn "Hello world %s" message
    0 // return an integer exit code
```

To run our project, we use `dotnet run` which will invoke `main` function in `Program.fs`

```bash
dotnet run
# Hello world from F#
```

## Importing files

To acquaint ourselves with how files and modules work in F#, let's create a second file.

```fsharp
// Utils.fs

module Utils

let permute list =
  let rec inserts e = function
    | [] -> [[e]]
    | x::xs as list -> (e::list)::[for xs' in inserts e xs -> x::xs']

  match List.length list with
  | 0 -> []
  | _ -> List.fold (fun accum x -> List.collect (inserts x) accum) [[]] list

```

Update `Program.fs` to use our `Utils` module

```fsharp
[<EntryPoint>]
let main argv =
    let input = argv.[0] |> List.ofSeq

    let listToStr = List.toArray >> System.String

    input
    |> Utils.permute 
    |> List.iter (listToStr >> printfn "%s")

    0 // return an integer exit code
```

Run the program again

```bash
dotnet run -- 123
# error FS0039: The value, namespace, type or module 'Utils' is not defined.
# The build failed. Fix the build errors and run again.
```

It seems that our build system is unable to resolve the newly created file which in turn led to `Utils` module not being recognized. Before we fix this, let's take a quick look at `hello-world.fsproj`

```xml
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net5.0</TargetFramework>
    <RootNamespace>hello_world</RootNamespace>
  </PropertyGroup>

  <ItemGroup>
    <Compile Include="Program.fs" />
  </ItemGroup>

</Project>
```

If you look carefully, you might notice that `ItemGroup` tag references `Program.fs`. F# build system uses this list to resolve files it needs to compile.

1. Files listed here must be sorted in topological order (no cyclic dependencies are allowed).
2. Any file listed here can be used without an import statement.
3. Whenever a file is moved to another directory, `fsproj.xml` is the only place you will need to update
4. There are [tools](https://github.com/fsprojects/Mechanic) to automate the process of populating `fsproj.xml`.

With that being said, let's update `fsproj.xml` to reference `Utils.fs`

```xml
  <ItemGroup>
    <Compile Include="Utils.fs" />
    <Compile Include="Program.fs" />
  </ItemGroup>
```

Running the program again should work without any errors

```bash
dotnet run -- 123
# 321
# 231
# 213
# 312
# 132
# 123
```

## Adding tests

We are going to use `NUnit` for our testing which means we need to install it along with some other packages

```bash
dotnet add package NUnit
dotnet add package NUnit3TestAdapter
dotnet add package Microsoft.NET.Test.Sdk
```

Check if your `fsproj.xml` references the new packages that were installed

```xml
  <ItemGroup>
    <Compile Include="Utils.fs" />
    <Compile Include="Program.fs" />
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
    <PackageReference Include="NUnit" Version="3.12.0" />
    <PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
  </ItemGroup>
```

And now it's time to write some unit tests. In F#, tests are normal functions annotated with the special `Test` attribute. This means were are free to write our unit-tests anywhere as long it is annotated properly. In this case, I chose to mimic Rust which stores its unit-tests in a nested module inside the same file that is being tested while storing integration-tests inside a `tests` folder.

```fsharp
// Utils.fs

module Utils

let permute list =
    // code omitted


module Tests =
    open NUnit.Framework

    [<Test>]
    let ``When an empty list is permuted expect to get empty lists`` () =
        let permutationCount = [] |> permute |> List.length
        Assert.AreEqual(0, permutationCount)

    [<Test>]
    let ``When [1;2;3] is permuted expect to get 6 lists`` () =
        let permutationCount = [1;2;3] |> permute |> List.length
        Assert.AreEqual(6, permutationCount)
``` 

We can use the following command to run our tests

```bash
dotnet test
# Starting test execution, please wait...
#
# A total of 1 test files matched the specified pattern.
#
# Test Run Successful.
# Total tests: 2
#      Passed: 2
# Total time: 3.3699 Seconds
```

## Building a shareable executable

We can use `dotnet publish` to build an executable that can be shared with the world. We are using `self-contained` flag to bundle .NET runtime with our executable. This will let us run our program in any environment regardless of the presence of .NET runtime or not. Note that this comes at expense of file size.

```bash
# Remember to customize -r flag according to your platform https://docs.microsoft.com/en-us/dotnet/core/rid-catalog
dotnet publish -c Release -r win10-x64 --self-contained
```

You can now publish the folder that is created at `hello-world\bin\Release\<dotnet version>\<rid>\`

## References
- https://fsharpforfunandprofit.com/posts/low-risk-ways-to-use-fsharp-at-work-3/
- https://softchris.github.io/pages/dotnet-10-commands.html
- https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-publish