This simple post shows how to use FSharpx.Extras for validation of rules for a simple Person type:

type Person =
  { Name: string  // Rule #1: Name should not be empty
    Age: uint8 }  // Rule #2: Age cannot be more than 140

See Section Project Setup/Development below for specific setup instructions for Ubuntu Linux 16.04 LTS.

We could use Discriminated Unions to easily represent the kind of errors we could get while creating records of this simple type:

type NameError = | NameEmpty

type AgeError = | AgeTooHigh

type PersonError =
    | NameE of NameError
    | AgeE of AgeError

By using FSharpx.Extras, we could use easily use F#’s Option to ensure our “business rules” are met:

open System
open FSharpx
open FSharpx.Option
open FSharpx.Functional

module WithOption =
    let check pred value =
        if pred value
        then Some value
        else None

    let personOptional name age =
        maybe {
            // Rule #1: Name should not be empty
            let! n = check (not << String.IsNullOrWhiteSpace) name
            // Rule #2: Age cannot be more than 140
            let! a = check (flip (<) 140uy) age
            return { Name = n; Age = a }
        }

The above personalOptional function, would create Person records only if all of our rules are met, otherwise, it will return None : Person Option.

The following snippet uses F#’s Choice to be able to encode the reason of failure:

open System
open FSharpx
open FSharpx.Choice
open FSharpx.Functional

module WithChoice =
    let check pred error value =
        if pred value
        then Choice1Of2 value
        else Choice2Of2 error

    let checkName = check (not << String.IsNullOrWhiteSpace) (NameE NameEmpty)
    let checkAge = check (flip (<) 140uy) (AgeE AgeTooHigh)

    let personChoice name age =
        choose {
            let! n = checkName  name
            let! a = checkAge age
            return { Name = n; Age = a }
        }

Notice that personChoice would short-circuit on the first failure check, but at least it will provide the reason of this first rule violation.

A better alternative, is to take advantage of the accumulation capabilities provided by Validation:

open System
open FSharpx
open FSharpx.Functional
open FSharpx.Collections
open FSharpx.Validation

module WithValidation =
    let validator pred error value =
        if pred value
        then Choice1Of2 value
        else Choice2Of2 (NonEmptyList.singleton error)

    let validateName =
        validator
            (not << String.IsNullOrWhiteSpace)
            (NameE NameEmpty)

    let validateAge =
        validator
            (flip (<) 140uy)
            (AgeE AgeTooHigh)

    let validatePerson (p: Person): Choice<Person, NonEmptyList<PersonError>> =
        returnM p
     <* validateName p.Name
     <* validateAge p.Age

Project Setup/Development

.NET development can be done crossplatform (at least, on Windows, Mac OS X and Linux).

The above snippets of code have been done using:

  • Ubuntu Linux 16.04.3 LTS
  • Mono 5.8.0.22
  • FSharpc 4.1
  • Ionide-fsharp 3.8.2
  • Ionide-FAKE 1.2.3
  • Ionide-Paket 1.7.3
  • dotnet-cli 2.0.2

I am going to (briefly) describe some of the setup steps done using the above.

Mono (alpha) and F#

For mono (alpha) on linux, follow the 3 steps given from the official mono page:

  1. Add the mono repository to your system
     sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF
     echo "deb http://download.mono-project.com/repo/ubuntu alpha-xenial main" | sudo tee /etc/apt/sources.list.d/mono-official-alpha.list
     sudo apt-get update
    
  2. Install mono: sudo apt-get install mono-devel
  3. Verify installation

Then install F# with sudo apt install fsharp.

Visual Studio Code with Ionide

You could install vscode with the following:

# Install key
curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.gpg
sudo mv microsoft.gpg /etc/apt/trusted.gpg.d/microsoft.gpg
# Install repo
sudo sh -c 'echo "deb [arch=amd64] https://packages.microsoft.com/repos/vscode stable main" > /etc/apt/sources.list.d/vscode.list'
# Update apt-get
sudo apt-get update
# Install
sudo apt-get install code # or code-insiders

Then, install Ionide for vscode using instructions given at ionide.io

dotnet-cli

Follow instructions from official Microsoft page here to install dotnet-cli, but check first if there is a newer version of dotnet sdk listed in the download archives. For example, I found instructions for version 2.0.2 here.

Even though a project created with Ionide is able to download and install dotnet-cli, it is useful to have the tool available in your path (and the command line).

Creation of a F# console project with Ionide (vscode)

After a correct installation of Ionide, you could use the command palette (Ctrl+Shift+P) to create a new F# console project:

  1. Open an empty folder somewhere (e.g. ~/gitRepos/dotnetWork/FSharpxConsole)
  2. Ctrl+Shift+P then type F# new project. Choose the empty folder from previous step, and a project name.
  3. Wait for some moments until Ionide opens a small notification that project has been created successfully.
  4. Ctrl+Shift+P then type fake build then choose the Build target.

The above process should finish successfully. Check the Output window (View > Output, or Ctrl+Alt+O on Linux to toggle) to verify that the Build target finished successfully.

Some other option is to open a console (you can also use vscode’s integrated terminal for that: View > Integrated Terminal), then type

./build.sh

(Personally I prefer the colored output visible from a terminal, and the integrated terminal is very handy).

Now, if you are going to add a .NET 4.6.1 dependency, for example, FSharpx.Extras, then you need to make a slight modification to your project’s .fsproj file. You need to tell dotnet-cli where your fsharp compiler is. Mine looks like this:

<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net461</TargetFramework>
    <DebugType>portable</DebugType>
  </PropertyGroup>

  <PropertyGroup>
    <IsWindows Condition="'$(OS)' == 'Windows_NT'">true</IsWindows>
    <IsOSX Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::OSX)))' == 'true'">true</IsOSX>
    <IsLinux Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true'">true</IsLinux>
  </PropertyGroup>  
  <PropertyGroup Condition="'$(IsWindows)' == 'true'">
    <FscToolPath>C:\Program Files (x86)\Microsoft SDKs\F#\4.1\Framework\v4.0</FscToolPath>
    <FscToolExe>fsc.exe</FscToolExe>
  </PropertyGroup>
  <PropertyGroup Condition="'$(IsOSX)' == 'true'">
    <FscToolPath>/Library/Frameworks/Mono.framework/Versions/Current/Commands</FscToolPath>
    <FscToolExe>fsharpc</FscToolExe>
  </PropertyGroup>
  <PropertyGroup Condition="'$(IsLinux)' == 'true'">
    <FscToolPath>/usr/bin</FscToolPath>
    <FscToolExe>fsharpc</FscToolExe>
  </PropertyGroup>

  <ItemGroup>
    <Compile Include="FSharpxConsole.fs" />
    <None Include="App.config" />
  </ItemGroup>
  <Import Project="..\.paket\Paket.Restore.targets" />
</Project>

I learned about that modification here.

Rebuild your project. It should compile properly before moving forward…

You may also need to set up several environment variables:

$ echo $FrameworkPathOverride
/usr/lib/mono/4.7-api
$ echo $LocalAppData
/home/oscarvarto
$ echo $APPDATA
/home/oscarvarto

Now, you are ready to add FSharpx.Extras to your console project.

  1. Modify paket.dependencies
     source https://www.nuget.org/api/v2
    
     framework: >= net461
     nuget FAKE
     nuget FSharp.Core
     nuget FSharpx.Extras
    
  2. Add FSharpx.Extras to your project’s paket.references Main group.
     FSharp.Core
    
     group Main
         FSharpx.Extras
    
  3. Use the command line to install/restore new dependencies

     mono .paket/paket.exe install
    
  4. Build your project

     ./build.sh