In a previous post, 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
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
// 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
dotnet run # Hello world from F#
To acquaint ourselves with how files and modules work in F#, let's create a second file.
// 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
Program.fs to use our
[<EntryPoint>] let main argv = let input = argv. |> 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
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
<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.
- Files listed here must be sorted in topological order (no cyclic dependencies are allowed).
- Any file listed here can be used without an import statement.
- Whenever a file is moved to another directory,
fsproj.xmlis the only place you will need to update
- There are tools to automate the process of populating
With that being said, let's update
fsproj.xml to reference
<ItemGroup> <Compile Include="Utils.fs" /> <Compile Include="Program.fs" /> </ItemGroup>
Running the program again should work without any errors
dotnet run -- 123 # 321 # 231 # 213 # 312 # 132 # 123
We are going to use
NUnit for our testing which means we need to install it along with some other packages
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
<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
// 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
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
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.
# 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