F#, Programming, Software design

Power of composition with map and bind

In functional architecture functionalities get composed into workflows. Workflows are essential part of any business behavior modeling. Things get complicated when you need to build bigger systems from small components. Sometimes it is hard to find proper connectors to fit multiple functions having different inputs and outputs. There are various tools to achieve that composition in FP world which you could have heard by the names like functors, monoids or monads. These tools allow you to glue things together by connecting outputs of one functions to the inputs of another functions with proper transformations in between. In practice it is much easier to understand how it works than diving in category theory and trying to figure out the math beneath it.

🔌 Composition basics

When dealing with relatively simple types like strings and numbers connecting inputs and outputs is quite straightforward. Consider this example:

let addOne a = a + 1
let multipleByTwo a = a * 2

Here we defined two functions both of which takes a number as an input and returns number as an output, so their signatures are the same – we expect number on input and the result of operation in output is also a number:

(int -> int)

We can call it in following ways:

multipleByTwo (addOne 2)
// OR
2 |> addOne |> multiplyByTwo
val it : int = 6

We also can create new function which is composition of addOne and multiplyByTwo:

let addOneMultipliedByTwo = addOne >> multiplyByTwo
addOneMultipliedByTwo 2

This way you can build really complex logic from smaller pieces just like with a Lego bricks.

🅰️ ADTs are everywhere

More often, however, you will find yourself writing a bit more complicated things than adding or multiplying numbers. It could be custom types, or types based on other types which are known as algebraic data types (ADTs). It is very common to build up things from abstract types and provide functions which transform other values to that types. One very familiar to you example could be Maybe (a.k.a. Option) type which you could heard as Maybe monad or Maybe functor. In a nutshell it is a container for value or absence of the value. This is extremely effective abstraction to avoid nulls in your code and hence having peace 🧘 and no null reference exceptions everywhere.

In F# it is presented in Option module with set of functions to work with that type. So you have type Option<‘T> with possible value Value: ‘T or no value which is None. You can find tons of functions in the module. They help you building more complex things from smaller and make proper transformations for connecting functions which require that type.

Let’s have a quick look on how to use it:

let someValue = Some 10
let noValue = None

someValue |> Option.get // val it : int = 10
someValue |> Option.isSome // val it : bool = true
noValue |> Option.isNone // val it : bool = true
(10, someValue) ||> Option.contains // val it : bool = true
(99, someValue) ||> Option.defaultValue // val it : int = 10
(99, noValue) ||> Option.defaultValue // val it : int = 99

😲 When things go wrong

Now let’s have a small programming exercise. Suppose silly scenario where we have a players (any game you can imagine) and we need to check if the score player collected is good or not. So we come up with something like this:

type Player = { 
    Name: string 
    Score: int
}

let isGoodScore score = if score >= 70 then true else false

So all we need is to create players and check their scores:

let frank = { Name = "Frank"; Score = 90; }
let jack = { Name = "Jack"; Score = 37; }

frank.Score |> isGoodScore // val it : bool = true
jack.Score |> isGoodScore // val it : bool = false

“Hey, but player could have no score as well”

So how about to support that? Well, piece of a cake. Let’s make few minor changes:

type Player = { 
    Name: string 
    Score: int option
}
let frank = { Name = "Frank"; Score = Some 90; }
let john = { Name = "John"; Score = None; }
let jack = { Name = "Jack"; Score = Some 37; }

Nice! We’ve wrapped score in Option type just exactly like in requirement we got. How about isGoodScore function, will it still work?

frank.Score |> isGoodScore
error FS0001: Type mismatch. Expecting a
    'int option -> 'a'
but given a
    'int -> bool'
The type 'int option' does not match the type 'int'

Oops, we can’t use optional type with plain type like that:

So we need a ways to glue up monadic types like Option with functions working on plain values. And that’s where two most essential functions get into the big picture: map and bind.

🤝When composition meet ADT

As I mentioned before in the FP toolbox there various tools to help us with transformations. One such tool is map function. There are other names for it like fmap, lift, Select (think of C# LINQ). Each monadic-like type has this function.

Let’s have a look what signature of that function for Option:

(('a -> 'b) -> 'a option -> 'b option)

There 3 arguments: function which transforms input of type ‘a to ‘b, optional ‘a and optional ‘b. So how can we apply map for our use case? Pretty straightforward actually:

frank.Score |> Option.map isGoodScore // val it : bool option = Some true
john.Score |> Option.map isGoodScore // val it : bool option = None
jack.Score |> Option.map isGoodScore // val it : bool option = Some false

You see how return type is changed? We just applied standalone function which works on int to Option type. It lifted result of the function execution back to the Option. If input value is Some int it will be extracted from container (Option type), piped to the function and on the other end lifted up/wrapped back to the Option type. In case if there no value, it will just use None.

In C# IEnumerable with Select method on it works in the same way but applied to collections which means that collections are also ADTs. Here some visuals to help in understanding what’s going on:

👷 Bind it

Another very useful tool is bind function which you may have heard by other names like flatMap, collect, SelectMany. This one allows you to compose monadic functions in a little bit different way. Here the signature of the bind function for Option:

(('a -> 'b option) -> 'a option -> 'b option)

Let’s extend on our previous example and say that now we have an external source (database, file, etc.) from which we need to fetch players to find out score. So we define tryFindPlayer function as follows:

let tryFindPlayer name = 
    [ frank; john; jack ] |> List.tryFind (fun c -> c.Name = name)

List.tryFind is built-in function which returns Some ‘T if satisfies predicate in lambda or None. In our case it will return Some Player or None. Now we would be able to get the score of the player:

tryFindPlayer "Frank"
    |> Option.bind (fun c -> c.Score)

Here the visuals:

As you see, unlike map, bind allows you to compose up things within the same category (Option) but with different underlying types. It is flattening result, so instead of having Option<Option<int>> with bind it skips unnecessary wrapping.

💪The power of composition

There a lot of ADTs in form of data structures, workflows and other concepts which you need to combine to build working software: List<T>, Option<T>, State<T>, Async<T>, etc.

Once you get a grasp on how to use it – it becomes straightforward how to compose things up:

tryFindPlayer "Frank" 
    |> Option.bind (fun c -> c.Score)
    |> Option.map isGoodScore
val it : bool option = Some true
.NET, F#, Programming

How to read settings from configuration file in F#

During the work on one of the projects I had to make connection to the SQL server to fetch data. Most of the development time I spent in F# interactive – I create some sort of scratchpad script file (with fsx extension) and run VSCode with Ionide extension. This works like a charm with all features you expect from modern code editor like autocomplete, linting and syntax highlighting. Having built-in REPL allows you to use NuGet packages, load files with F# code, reference managed assemblies and execute selected parts of the code by pressing Alt+Enter directly in editor.

During development you could keep connection string in constant or variable, but at some stage, when you finalize project you want to move everything to config file. There is a problem related to this however. The way how default executable treated depends on the context. In case of F# project the default executable is the current project .config file. In case of F# interactive it is Fsi.exe.config. So solution which works fine for your F# project will fail when you run from F# interactive. I will show you how you can make it work in both contexts.

So, how to read configuration file in your F# project? Well, one great and simple option is just to use AppSettings type provider. It will expose your app.config in a strongly typed way. If you don’t know what type providers are please refer to the documentation. There is no direct analogy in C# to this concept. As author of F# language said:

A design-time component that computes a space of types and methods on-demand…

An adapter between data/services and the .NET type system…

On-demand, scalable compile-time provision of type/module definitions…

Don Syme

However in this post I would like to show you how you can create a simple abstraction to read connection string (or any other section like appSettings) and what caveats are on your way. Assume we have following app.config file in the root folder of our demo app:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <connectionStrings>
        <add name="NinjaConnectionString" connectionString="Server=(localdb)\MsSqlLocalDb;Database=NinjaDb;Trusted_Connection=True;"/>
    </connectionStrings>
</configuration>

Solution for F# projects

Let’s create Configuration.fs file and start with class definition for our configuration abstraction:

type NinjaConfiguration() = class
    static member ConnectionString = ()
end

Ok, now we need a function to read a config file (assuming you have your configuration file in bin folder and named {project-executable}.config). Just add this section to your fsproj to copy app.config from your project’s root to bin on each build:

<Target Name="CopyCustomContent" AfterTargets="AfterBuild">
        <Copy SourceFiles="app.config" DestinationFiles="$(OutDir)\ninja_app.dll.config" />
</Target>

The function to read connection strings could look like this:

let private tryGetConnectionString (connectionStrings: ConnectionStringSettingsCollection) name =
    seq { for i in 0..connectionStrings.Count - 1 -> connectionStrings.[i] }
    |> Seq.tryFind(fun cfg -> cfg.Name = name)
    |> function
    | Some cs -> Some cs.ConnectionString
    | _ -> None

The signature of the function is

(ConnectionStringSettingsCollection -> string -> string option)

It takes ConnectionStringSettingsCollection and name of the connection element in your app.config and returns option of string with it’s value or None.

On line 2 we create a F# sequence expression to wrap standard .NET collection type. This will allow us to use any idiomatic F# language constructs which applicable to collections (think of all functions in Seq module, pipe operator, etc.).

On line 3 we immediately benefit from it by piping all elements from connection string section to Seq.tryFind and using lambda function to find only setting we need by name parameter. This will iterate over all entries in connection strings and compare it against Name property of ConnectionStringSettings class. If it finds an entry, Some of ConnectionStringSettings will be returned, otherwise None.

Lines 4-6 just extract connection string from it with a simple pattern matching.

Let’s update NinjaConfiguration class:

type NinjaConfiguration() = class
    static member ConnectionString = 
        tryGetConnectionString ConfigurationManager.ConnectionStrings "NinjaConnectionString"
end

This is already working code, however without error handling it is not complete, so let’s add try-with section to be sure that when file is missing we not bubble up runtime exception in your face:

type NinjaConfiguration() = class
    static member ConnectionString = 
        try
            tryGetConnectionString ConfigurationManager.ConnectionStrings "NinjaConnectionString"
        with
            | Failure (_) -> None
end

Much better. If there is a problem with finding or opening configuration file we return None. Same for the case when there no connection string with NinjaConnectionString name found. Put it all together we should come up with this code:

module Ninja.Configuration

open System.Configuration

let private tryGetConnectionString (connectionStrings: ConnectionStringSettingsCollection) name =
    seq { for i in 0..connectionStrings.Count - 1 -> connectionStrings.[i] }
    |> Seq.tryFind(fun cfg -> cfg.Name = name)
    |> function
    | Some cs -> Some cs.ConnectionString
    | _ -> None

type NinjaConfiguration() = class
    static member ConnectionString =
        try
            tryGetConnectionString ConfigurationManager.ConnectionStrings "NinjaConnectionString"
        with
           | Failure(_) -> None
end

Extending solution to work in F# interactive

Previous solution works fine when you run it with F5 in VSCode or Visual Studio IDE or via dotnet run command line. But how to make it work in F# interactive?

Let’s create simple scratchpad.fsx to use NinjaConfiguration in F# interactive:

#r "nuget: System.Configuration.ConfigurationManager" // install NuGet package needed for Configuration.fs
#load "Configuration.fs" // load our NinjaConfiguration class

open Ninja.Configuration // open module so that type will be available for use
let connStr = NinjaConfiguration.ConnectionString

val connStr : string option = None. 

App.config in the same folder where scratchpad.fsx and Configuration.fs. So why result is None? The answer is that default path for lookup will be fsi.exe and since we used ConfigurationManager.ConnectionStrings it will start search config file from global scope (machine.config). So to solve that issue we need to set current directory for F# interactive and map configuration file to that folder. To make it work in both contexts we need to add conditional compiler directive (let’s call it COMPILED). Let’s make final changes to our code in Configuration.fs to the following snippet:

module Ninja.Configuration

open System.Configuration

let [<Literal>] private DbConnectionStringName = "NinjaConnectionString"

let private tryGetConnectionString (connectionStrings: ConnectionStringSettingsCollection) name =
    seq { for i in 0..connectionStrings.Count - 1 -> connectionStrings.[i] }
    |> Seq.tryFind(fun cfg -> cfg.Name = name)
    |> function
    | Some cs -> Some cs.ConnectionString
    | _ -> None

type NinjaConfiguration() = class
    static member ConnectionString =
        try
            // Executes in F# project/solution when provided COMPILED compilation directive
            #if COMPILED 
                tryGetConnectionString ConfigurationManager.ConnectionStrings DbConnectionStringName
            #else // Executes in script environment (fsx file)
                System.IO.Directory.SetCurrentDirectory (__SOURCE_DIRECTORY__)
                let fileMap = ExeConfigurationFileMap()
                fileMap.ExeConfigFilename <- "app.config"
                let config = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None)
                tryGetConnectionString config.ConnectionStrings.ConnectionStrings DbConnectionStringName
            #endif
        with
           | Failure(_) -> None
end

After change re-execute following lines in the script:

#load "Configuration.fs" // load our NinjaConfiguration class

open Ninja.Configuration // open module so that type will be available for use
let connStr = NinjaConfiguration.ConnectionString

Now result is:

val connStr: string option = Some "Server=(localdb)\MsSqlLocalDb;Database=NinjaDb;Trusted_Connection=True;"

After adjustments the code in Configuration.fs will work in both cases: as a part of F# project or in F# interactive. Same principle applies to any IO: if you want your code to work in both contexts you need to take this in consideration.

Happy coding!