Visualizing software architecture using the C4 model

Recently I stumbled across a model called ”C4” that is designed to help visualize software architecture. After reading through the C4 website, https://c4model.com/, and looking at the conference talk by Simon Brown, I felt that this was something I should explore more in depth. My, very personal, opinion is that we as software designers have lost our ability to communicate software architecture in clear and concise way. Maybe it was lost somewhere in our hunt for being ”Agile” and valuing Working Software over comprehensive documentation, as stated in the Manifesto for Agile Software Development, and we need to rediscover how it should be done.

Now, I don’t have anything against being agile, I’ve suffered through the test and integration phases of a large waterfall project and that is one experience I never wish to have again. However, valuing Working Software does not mean that our systems should not be documented, and in many cases claiming that The code is our documentation just does not cut it (if you don’t believe me, try telling your, not so tech-savvy, manager to clone the Git repo and look at the source code when you get a question about the system and see what happens).

What really spoke to me when I learned about the C4 model are the clear and distinct levels, Context, Containers, Components, and Code. These are ordered by level of abstraction. At the highest level, Context, the system is presented as a single box and the diagram shows people and external systems that in some way interact with the system. As an example I made up a used cars dealership backend system.

System context diagram for the imaginary system

It should be easy to understand the role of the system and the context of which it functions. In the diagram there are two Persons, or roles, a Car Dealer and a System Admin. The dealer has access to a locally hosted system which in turn interacts with the backend system via REST. The system admin has direct access to the backend system via some sort of graphical user interface (GUI). The backend system also interacts with the National vehicle owner registry via remote procedure calls (RPC).

On the next level, the Container level, we open up the box of the Used Cars Dealership Backend system and take a look inside. Note that containers in this context have nothing to do with Docker. Containers in this context are different applications that makes up the system. You can imagine a Web API application that handles REST request and some sort of admin application that the system administrator has access to. There is probably also one or two databases that contains information about the cars and the connected dealers. It could look something like the diagram below.

Container diagram for the imaginary system

On the third level, Components, we zoom in on a single container and peek inside. I didn’t go that deep in this example but you can imagine a component to correspond to a .NET project and the connections between components being project references. As you can imagine, the components level can change quite rapidly in new projects and it can be worth looking at ways to generate a diagram in this level instead of manually keeping it updated.

For the final level, Code, no diagrams should be manually constructed. The Code level is represented by classes and interfaces, which can be constructed using UML. These diagrams should either be skipped entirely or generated by a tool.

Conclusion

The C4 model can be really useful for describing a software system’s architecture from a high-level perspective. The first two levels, Context and Containers, should be quite stable during the lifetime of a system and creating and keeping those diagrams up to date should not add much work. The diagrams can be used when communicating the architecture both within a development team and with other parts of the organization.

The lower two levels, Components and Code, are more volatile and also more targeting software developers and architects actively working with developing the system. They should be generated from the existing code rather than being written manually by hand.

For documentation on Wiki pages, PDF files and the likes I would only use Context and Container diagrams. Components and Code can be generated when needed, or automatically generated as part of the build and automatically uploaded to a shared area.

There are pretty good tooling available for creating the diagrams using simple text and have the diagrams generated. For the diagrams above I used PlantUML (https://plantuml.com/) with a C4 add-on (https://github.com/RicardoNiepel/C4-PlantUML). The text files can be easily stored in the same source control repository as the system’s source code.

Book review – Get Programming with F#

It’s been a while since I finished Get Programming with F# by Isaac Abraham but I haven’t come around to review it, until now.

Learning functional programming has been one of my personal goals this year. I started out with Functional Programming in C# by Enrico Buonanno which explains a lot of the concepts of functional programming, and shows how they can be implemented in C# (see my review here https://ericbackhage.net/c/book-review-functional-programming-in-c/). It was nice to be able to learn the concepts without needing to pick up a new programming language at the same time. However, since C# is primarily an object oriented language, things like immutability, partial application, currying, etc does not fit very well and does not come naturally. So what better way to get some real experience than to learn a functional first language?

There are several languages to choose from when you wish to pick up a functional language. For the purist there is Haskell (https://www.haskell.org/) which is a purely functional programming language. If you wish to run on the JVM you can give Scala (https://scala-lang.org/) or Clojure (https://clojure.org/) a go. For me, who primarily writes code that runs on the CLR, F# felt like a natural language to start out with. Any libraries that you write in F# can be used from C# projects as well, and you can have both C# and F# projects in the same solution in Visual Studio and have them reference each other. Hence, I decided to Get Programming with F#.

Book cover

The subtitle of the book is A guide for .NET developers and that is exactly the target audience. I would say that a prerequisite for getting real value from this book is that you are familiar with writing code in C# using Visual Studio.

It is written similar to a hands-on tutorial where new material is presented and you are given tasks where you need to apply the knowledge. I like the idea where you follow along and put your new found knowledge into practice as you go, but it also put constraints on when and where you can study the material since you need to sit by your computer pretty much the whole time.

The book was published in 2018 and most of the material is still relevant. However, there are some parts that are out of date, especially those that describes how to set up your work environment for working efficiently with F#. This is quite frustrating since this is one of the first things you encounter in the book and it does give you a bad start. Once you realize that Visual F# Power Tools is no longer supported and either just move along without it, or switch to Visual Studio Code with Ionide (https://ionide.io/), everything goes pretty well, until you need to reference libraries from F# interactive. I struggled with this quite a bit and ended up using nuget on command line to download the packages to a temporary folder which I then referenced. If you know of a better way, please let me know.

The material covered is all good stuff. I was very impressed with the type providers and I can see how these can be very powerful tools. Even though F# is a multi-paradigm language the author focuses on the functional style and avoids using object oriented solutions. This is a good thing since it shows how to solve common problems in a different way than you are used to, coming from C#.

I am not aware of any other book on F# that is quite like this one and I would recommend it to anyone with a solid background in C# development that wish to get some hands on experience with functional programming in F#. It is a bit frustrating that some parts are out of date but if you are aware of it you can quite easily just skip those parts. Also, the code for the book could use some updating, there is however a separate branch for .NET Core that I recommend you switch to when working through the examples (https://github.com/isaacabraham/get-programming-fsharp/tree/netcore).

If you really want to go hardcore I believe Haskell is the way to go. F# is not purely functional and will let you do things, like calling impure functions from pure functions, that won’t even compile when using Haskell. I do however recommend that you try to adhere to functional principles also when learning F#. If you want to write code using object oriented principles then C# is a much better fit.

In summary, if you are a C# developer interested in F#, this book is for you.

New C# 9 features and their F# counterparts

I recently watched Mads Torgersen’s video presentation of C# 9 from Microsoft Build and also read his presentation on the same topic titled Welcome to C# 9.0. It is obvious that the C# team is heavily inspired by functional programming and many of the new features have equivalents in F# (where they have existed for a long time). I thought it would be interesting to do a comparison of the new features and their F# counterparts. Since C# is primarily an object oriented language it might be difficult to add functional concepts in a way that fits well into the existing language. Let’s see what it looks like.

Init-only properties

Several of the new C# 9 features focuses on immutability. For those of you familiar with functional programming it is no news that immutability is one of the ground pillars of the functional paradigm. Mutating data is something we associate with imperative and object oriented programming.

C# 9 will introduce a feature called init-only properties which limits the possibility to set property values to only when a new instance of an object is created. Let’s look at some code.

// Prior to C# 9
public class Person 
{
  public string FirstName { get; set; }
  public string LastName { get; set; }
}
var person = new Person { FirstName = "Eric", LastName = "Backhage" };
// It is possible to change the value of the properties whenever, this
// might not be something you want.
person.FirstName = "Carl";

// With C# 9
public class Person
{
  // Notice that 'set' has been changed to 'init'
  public string FirstName { get; init; }
  public string LastName { get; init; }
}
var person = new Person { FirstName = "Eric", LastName = "Backhage" };
// With 'set' changed to 'init' the following line will not compile.
person.FirstName = "Carl";
// However, it is still possible to set the 'person' variable to
// a different value.
person = new Person { FirstName = "Carl", LastName = "Backhage" }; 

F# – Record type assigned to mutable variable

If I wanted to have similar behavior in F# I would use a record type and assign it to a variable that is declared as mutable. The code would look like this:

type Person = { FirstName: string; LastName: string }
let mutable person = { FirstName = "Eric"; LastName = "Backhage" }
// As in the C# version the following line would not compile
person.FirstName <- "Carl"
// But it is still possible to set the 'person' variable to a
// different value
person <- { FirstName = "Carl"; LastName = "Backhage" }

The only reason I would use ’mutable’ however is for variables that are updated thousands of times in a tight loop. In other cases I would use the default behavior of immutable variables. We’ll see this next.

Records

Records in C# has been planned for a long time and it feels really nice that they finally seem to make it into the language. For those of you unfamiliar with Records they can be described as immutable data containers without any logic (methods) associated with them. To define a Record type in C# 9 you add the keyword data to the class definition. Like this:

public data class Person
{
  public string FirstName { get; init; }
  public string LastName { get; init; }
}
/* 
  There is also a shorthand that removes most of the 
   boiler plate. The above code can be written like
   this instead:
   public data class Person { string FirstName; string LastName }
*/
// Creating an instance of Person is no different
// than before
var person = new Person { FirstName = "Eric", LastName = "Backhage" };
// Properties are still init-only so the following line
// will not compile
person.FirstName = "Carl";
// However, since Person is now a Record the
// following line will also not compile
person = new Person { FirstName = "Carl", LastName = "Backhage" };
// Instead you must introduce a new variable
var anotherPerson = person with { FirstName = "Carl" };
// The 'with' keyword lets you clone properties from another
// instance. anotherPerson.LastName is equal to "Backhage"

F# – Records

In F# Records is one of the core language types and has been around forever. The above C# 9 code looks like this in F#:

type Person = { FirstName: string; LastName: string }
let person = { FirstName = "Eric"; LastName = "Backhage" }
// As in C# 9 the following does not compile
person.FirstName <- "Carl"
person <- { FirstName = "Carl"; LastName = "Backhage" }
// Instead you need to introduce a new variable
let anotherPerson = { person with FirstName = "Carl" }

C# 9 Records – value based equality

Unlike regular classes, that uses reference based equality unless you override the Equals method, C# 9 Records will use value based equality. That means that if two records are of the same type and their properties have the same values, they will be considered equal. Let’s look at some example code.

public data class Person { string FirstName; string LastName; }
var p1 = new Person { FirstName = "Eric", LastName = "Backhage" };
var p2 = new Person { FirstName = "Eric", LastName = "Backhage" };
var p3 = new Person { FirstName = "Carl", LastName = "Karlsson" };

p1.Equals(p2); // => true
p2.Equals(p3); // => false

In F#, Records works exactly in the same way, you just use ’=’ for equality checking instead:

type Person { FirstName: string; LastName: string }
let p1 = { FirstName = "Eric"; LastName = "Backhage" }
let p2 = { FirstName = "Eric"; LastName = "Backhage" }
let p3 = { FirstName = "Carl"; LastName = "Karlsson" }

p1 = p2 // -> true
p2 = p3 // -> false

C# 9 Records – Positional Records

Positional Records is a nice shorthand that reduces the amount of boiler plate code needed when defining record types with constructors and deconstructors. The code example below shows how it will work.

public data class Person(string FirstName, string LastName);

/* The code above is actually equal to all of this:
public data class Person
{
  string FirstName;
  string LastName;
  public Person(string firstName, string lastName)
    => (FirstName, LastName) = (firstName, lastName);
  public void Deconstruct(out string firstName, out string lastName)
    => (firstName, lastName) = (FirstName, LastName);
}
*/
// Instantiating
var person = new Person("Eric", "Backhage");
// Deconstructing
var (firstName, lastName) = person;

Now we are getting on par with F# in terms of being succinct. Let’s compare the same operations line by line:

// C# 9
public data class Person(string FirstName, string LastName);
// F#
type Person { FirstName: string; LastName: string }
// C# 9
var person = new Person("Eric", "Backhage");
// F#
let person = { FirstName = "Eric"; LastName = "Backhage" };
// C# 9
var (firstName, lastName) = person;
// F#
let { FirstName = firstName; LastName = lastName } = person

Nice work of the C# team!

C# 9 Records – Inheritance

True to the object oriented paradigm, C# Records will support inheritance. You will be able to create base Records and create derivatives that inherit from them. It will look like this:

public data class Person { string FirstName; string LastName; }
public data class Student : Person { int ID; }

var student = new Student { FirstName = "Eric", LastName = "Backhage", ID = 1 };

Since inheritance is an object oriented concept and F# is a functional-first language F# Records can not be inherited. The solution is to use composition instead. One way of accomplishing similar behavior in F# is shown in the code below:

type PersonalInfo = { FirstName: string; LastName: string }
type Student = { ID: int; PersonalInfo: PersonalInfo }

let student = { ID = 1; PersonalInfo = { FirstName = "Eric"; LastName = "Backhage" } }

My personal preference here is composition. I believe that the C# team have had a really tough time getting value based equality and instantiation using the ’with’ keyword working well with inheritance, hope it will be worth it.

Conclusion and further reading

I think the new C# 9 features will be great additions to the C# language and it is my opinion that the C# language design team are doing a great job adding nice new features to the language. Immutable records are not something you would normally expect to find in an object oriented language, and they feel a lot more natural in F#, but I am confident they will make a great addition to C# now that they are finally coming.

For more information on upcoming C# 9 features that I haven’t mentioned here I recommend reading Mads Torgersens post on (from where I copied most C# 9 examples in the code in this post) https://devblogs.microsoft.com/dotnet/welcome-to-c-9-0/.

Now, stop reading and go write some code!

Introducing The WIB Limit

If you and your development team are using a Kanban board then you are familiar with the term WIP limit. WIP is short for Work In Progress and the idea is that putting a limit on the amount of ongoing work will help the team focus on the most important work and getting more work items completed. The limits will also help with identifying bottlenecks in the work flow that needs to be addressed.

I think WIP limits are great. Personally I try to work on only one thing at the time as much as possible and minimize context switching. There is however one more limit that I would like to propose, the WIB limit. WIB is short for ’Work In the Backlog’ and the goal of the limit is to avoid having the number of items in your backlog growing out of control.

The backlog problem

In my experience, backlogs with 100 or even several hundred items in them are not unusual. As time goes by more and more items are added, at a faster pace than the development team can finish the items on top. This usually goes on until the product owner goes on a cleaning frenzy and drops the majority of the items. The cycle then starts over and the backlog continues to grow.

So why is this a problem? For once, if you get a request for a feature from the organization and your response is ”Sure! I am putting it in the backlog and we’ll work on it as soon as possible”, and you have 100 other items in the backlog with higher priority, you are pretty much lying to the organization that the feature will be developed…ever. And once people starts to realize that their items aren’t being worked on they will try to find all kinds of ways around your work process.

Second, trying to keep an overview of, and prioritize between, hundreds of items is just not realistic. The work effort required to keep them up to date and in correct priority order is just too big.

Third, there will be times when you start feeling guilty for having items just lingering in the backlog for months and months, but because they have been there for so long the notes written in them will be out of date or difficult to remember what they actually meant, meaning you have to start over with any work you put in to understanding requirements and write a good user story in the first place.

The WIB limit

I suggest putting a hard limit on the number of items in the backlog. There is no exact number that I believe works for all but somewhere in the range 15 – 20 items should be the limit. It should be possible to have a pretty good overview on 20 items and keeping them prioritized.

Once the WIB limit has been reached, no new items are added to the backlog until there is free space. Any requests to add new items are rejected, justified by that there just isn’t any room for new work items at this moment.

Now, there will be times when something really urgent pops up that needs to be put high up in the backlog. When this happens, and the WIB limit has already been reached, the item with the lowest priority must be dropped and the organization has to be informed that this happened. If the item put in is really top priority, then this must be accepted.

Note that introducing a WIB limit will not in any way reduce the number of work items being completed. Instead it will help with highlighting the current work load, make it more clear what items are prioritized and why, and keep development teams from making promises they can not keep.

Learn SQL Server Administration in a Month of Lunches

I just finished a month of lunches (actually breakfasts) in the company of a book called ”Learn SQL Server Administration in a Month of Lunches” by Don Jones. As the name implies it is a book on the topic of administrating one or more database servers that runs Microsofts RDBMS software ”SQL Server”.

Book cover

This is NOT a book on database design or the programming language SQL, even though it contains a T-SQL crash course. Instead this book teaches you how to manage a SQL Server. That is, how to manage security, set up and configure auditing, monitor performance, investigate dead locks and time outs, manage back-ups, and other things that are useful for keeping a database server running smoothly.

Jones did not write this books for software developers only but for anyone who recently has become responsible for administrating a server running SQL Server and is in need of getting up to speed with the most important tasks that comes with the role. I would recommend anyone, developer or not, that are responsible for managing one or more instances of SQL Server to read this book. It is packed with tips and hands-on exercises that will quickly help you learn the most important skills needed for administrating SQL Server.

Even though the book was written when SQL Server 2012 was the most recent version out there I did not find anything that wasn’t applicable for SQL Server 2019. Even the pictures of dialogs in SQL Server Management Studio are still 100% accurate. I suppose that Microsoft does not change these if not absolutely necessary.

Now go learn some SQL Server administration!

Results in F#

I recently wrote a blog post on the Option type in F# and how to use it to represent the abscence of a value, for example when looking up a user in a database by trying to match an integer with a user id. If no user with a matching id exists, Option lets you return None. Compared to returning null, which is quite common in C#, None is a valid value and you wont risk getting a NullReferenceException when returning None.

However, Option does not let you specify any reason, like an error message, that explains what went wrong when None was returned. To remedy that, you can instead use the Result type. A Result in F# can be either Ok or Error where both can be of different types. Let’s look at the example from before, but re-written to use Result.

open System

type CustomerId = CustomerId of uint32
type Customer = { Id: CustomerId }
type Email = Email of string
type ErrorMessage = ErrorMessage of string

type StringToCustomerIdParser = string -> Result<CustomerId, ErrorMessage>
type CustomerLookup = CustomerId -> Result<Customer, ErrorMessage>
type EmailLookup = Customer -> Result<Email, ErrorMessage>
type EmailSender = Email -> unit

let toError s = ErrorMessage s |> Error
let toString (ErrorMessage msg) = msg

let userIdStringParser : StringToCustomerIdParser = fun s ->
    match UInt32.TryParse s with
    | true, num -> Ok (CustomerId num)
    | _ -> toError (sprintf "Could not parse %s to an integer" s)

let getCustomerById : CustomerLookup = fun id ->
    let getValue (CustomerId i) = i

    match id with
    | CustomerId 1u -> Ok { Id = CustomerId 1u }
    | CustomerId 2u -> Ok { Id = CustomerId 2u }
    | _ -> toError (sprintf "No customer with id %d exists" (getValue id))

let getEmailForCustomer : EmailLookup = fun customer ->
    match customer.Id with
    | CustomerId 1u -> Ok (Email "apan@bepan.se")
    | _  -> toError "Customer has no e-mail"

let sendPromotionalEmail : EmailSender = fun email ->
    ()

[<EntryPoint>]
let main argv =
    if argv.Length <> 1 then
        failwith "Usage: program <user_id>\n"

    let result = 
        userIdStringParser argv.[0]
        |> Result.bind getCustomerById 
        |> Result.bind getEmailForCustomer
        |> Result.map sendPromotionalEmail

    match result with
    | Ok _ -> printf "Email sent successfully\n"
    | Error err -> printf "%s"  (toString err)
    0 

Take a look at the code in the main function. There are several methods that return a Result and I have used Result.bind and map to create a workflow where the returnvalue from each function call is used as an input parameter to the next function, as long as the result is Ok. If any of the functions returns an Error the rest of the workflow is skipped (Result.bind will not call the next function, instead it will just return the Error from the previous function).

A constraint that is good to be aware of is that the error value type must be the same for all functions. You can see this if you look at the function type declarations that the error value type is always ErrorMessage, which is a type that just wraps a string. If different types were used it would not be possible for Result.bind and map to just return the error from the previous function, since the types would not match. There are ways around this but I won’t go into those now.

Looking at the code again. What do you think will be printed to the console if the number one is given as input, what about 2 and 3? Below is the outcome from some different runs.

$ sendEmailToCustomer 1
Email sent successfully
$ sendEmailToCustomer 2
Customer has no e-mail
$ sendEmailToCustomer 3
No customer with id 3 exists
$ sendEmailToCustomer test
Could not parse 'test' to an integer

How do you like this way of handling errors? Quite different from the usual throwing exceptions and returning null isn’t it. Personally I think that, after a bit of getting used to, this approach makes the code much easier to read and reason about. Notice that there is not a single try-catch, no null guards, and only a single if-statement in the code above.

Physical books in a connected world

Some might say that technical books, in the form that is printed on dead trees, have lost their raison d’être in the connected world we’re living in. To some extent they are right, there are many cases where it is better to look up the latest information online than to confide to a dusty old reference manual. In other cases a proper book will serve you much better than an online resource.

Some of the books in my shelf

For once, the quality level that has to be met in order to get a technical book published is just so much higher than anything posted online. If you buy a technical book you can be pretty sure that the author is an expert in his field and that and least one, in many cases several, other experts have been involved and given their input and approval.

Secondly, with a book you have all information collected in one place, usually nicely structured and with a comprehensive index at the back. I often find it just as easy to look up something in a book than it is to search for it online and in many cases the descriptions and examples in the books are much more educational which helps with the understanding on why things work as they do and not just how they work.

An argument you some times hear against buying technical books is that ”technology changes so fast that your books will be out of date before you have finished reading them”. In some cases this is true, in many cases not. The foundations on which most of the new technology is built upon have not changed in decades. Take the major programming paradigms as an example. Procedural, object oriented, and functional programming were all invented in the 50’s and 60’s and the core concepts are still the same. Hence, learning the core concepts is useful and will stay useful, regardless of the programming language you write your programs in (this is by the way something that is often overlooked when recruiting software developers where recruiters often look for experience with a particular programming language over a high level of understanding of the programming paradigm. The language used is often a minor detail, a good Java programmer will pick up C# in no time, and vice versa).
Other areas that haven’t aged much are data structures, algorithms, and relational databases. Sure, SQL server is always evolving and NOSQL databases has gained a lot of popularity recently, but the foundations laid out by Edgar F. Codd in the 1970’s are still highly relevant.

In conclusion, traditional technical books still has a place in the modern connected world. They are great for educational purposes when the goal is to learn about concepts and ideas that has proven to stay true over the years. It is also a nice feeling to grab a cup of coffee, pick up a real physical book, and flip between actual pages at a peaceful pace.

Options in F#

In the last post I compared the new Nullable reference types that were introduced in C# 8 with using Option from language-ext, which tries to mimic the functional paradigm approach to representing the abscence of a value. One of the major drawbacks of Option in C# is that there is no built in language-support, instead you have to rely on third party packages, or roll your own.

In F# however, as well as in other functional languages, Option is built in from the start and is the default way to represent a missing value. I usually don’t write code in F# but I wanted to take a look on how the scenario I used in the previous post would look like in a functional-first language so I made a test implementation in F#. Now this is just dummy code to test the scenario, in a real world application the functions would look a lot different:

open System

type UserId = UserId of uint32

type Customer =
    { Id: UserId }

type Email = Email of string

let tryParse (s: string) : UserId option =
    match UInt32.TryParse s with
    | true, num -> Some (UserId num)
    | _ -> None

let getCustomerById (id: UserId) : Customer option =
    match id with
    | UserId 1u -> Some { Id = UserId 1u }
    | UserId 2u -> Some { Id = UserId 2u }
    | _ -> None 

let getEmailForCustomer (customer: Customer) : Email option =
    match customer.Id with
    | UserId 1u -> Some (Email "apan@bepan.se")
    | _  -> None

let sendPromotionalEmail (email: Email) : unit =
    ()

[<EntryPoint>]
let main argv =
    if argv.Length <> 1 then
        failwith "Usage: program <user_id>"

    let result = 
        tryParse argv.[0]
        |> Option.bind getCustomerById 
        |> Option.bind getEmailForCustomer
        |> Option.map sendPromotionalEmail

    match result with
    | Some _ -> printfn "Email sent successfully"
    | None -> printfn "No email sent"
    0 

Note that even though F# is a statically typed language it is not required to explicitly define types for all input parameters and return values, most of the time the compiler can resolve the types so you don’t have to. In the code above I have however defined the types anyway to make it clearer how to work with Option, bind and map.

As you can see there are three functions that returns Options, tryParse that returns Option<CustomerId>, getCustomerById that returns Option<Customer>, and getEmailForCustomer that returns Option<Email>. Note that even though none of the functions takes an Option as input we are able to pipe the returned value through the functions in the main function using Option.bind and Option.map. Just like we were able to do in C# using language-ext.

Conclusion

In this simple scenario it was quite simple to port the C# implemenation to F#, so much that I would like to explore other scenarios as well and how to implement them using F#.

If using F# is an option it might be worth considering it, and get the full experience, over trying to copy the behavior by using language-ext. I will continue exploring F# and how it compares to C# in different scenarios. I believe it will be an interesting journey.

Nullable reference types compared to the Option monad

I wanted to investigate how Nullable reference types, the new big feature that was introduced in C# 8, compared to the Option type from language-ext. But let’s start with some information to set up the scene.

Nullable reference types

The way to indicate the abscence of a value in C#, as well as in many other languages, is usually to use null. For example, assume that we wish to look up a user in the database by supplying a user id. The method signature might look something like this:

public User GetUserById(int userId);

Now, this signature is a bit problematic since it does not say anything about what will happen when no user with the given userId exists. It might throw an exception, it might return a default User, or it might return null. The only way to really know is to look at the implementation. Also, if null is returned and the calling method doesn’t cover this case, we might end up with the dreaded null reference exception.

In C# 8 these problems have been addressed, by the introduction of Nullable reference types. If you turn on this feature in your project (see https://docs.microsoft.com/en-us/dotnet/csharp/nullable-references for details) the GetUserById method can be declared like this instead:

public User? GetUserById(int userId);

Notice the questionmark after User? That indicates that this method might return null. This is a strong indication to what will happen if no user with the given user id exists. Also, the compiler will now generate warnings if you try to deference the returnvalue without first checking for null. This is nice!

The Option monad

Now, don’t be scared of the M-word. You don’t need to understand what a monad is for the following text to make sense. Later in this post I will, however, show some nice things that comes from this, but you still don’t need to understand all the underlying details (in fact, if you’re a C# developer you probably use monads all the time without even knowing it. Every type that implements IEnumerable is actually a monad).

Anyway, the idea of using an Option type comes from the functional paradigm. In F#, as an example, the option type is part of the language out of the box. In C# you can find a nice implementation of it in language-ext, which is available as a nuget package from the official nuget repository.

Using the option type the GetUserById method would get a signature like this:

public Option<User> GetUserById(int userId);

The Option type is like a container that can be either empty or contain a single value. Just like the nullable reference type, using an option is a clear indication to the caller to what to expect in the case where no user with the given user id exists.

And, just like the nullable reference type, the calling code must handle the possibility that no user is returned. Actually, using option is even more secure since there is no way to get the code to even compile without handling both possible outcomes.

Comparing the two

So, are there any benefits with using nullable reference types over the option monad, or the other way around? Well, the obvious benefit of nullable reference types is that it has built-in language support in C# starting from C# 8. No need for extra nuget packages, only a quick change in the project file and it’s enabled. It is even possible to gradually introduce it by using more fine grained control mechanisms than entire projects.

Also, nullable reference types is probably a lot easier for existing C# programmers to accept than introducing the Option type, which might look and feel a bit strange and takes a while to understand and see the benefits of. It sure feels like nullable reference types is the better option here (no pun intended).

Now, with that said, you might take the blue pill, stop reading here and remain in ignorence.
Or, you may take the red pill and I’ll show you how deep the rabbit-hole goes (did not get the reference? Shame on you! Go see The Matrix and come back here afterwards).

The problem with null is that it has no value, it’s the abscence of a value. An Option on the other hand is always a valid instance. In language-ext the Option type is implemented as a struct, meaning it can’t be null. Also, in case the Option contains a value, that value can also not be null. The implementation of Option stops any attempts to create a new Option containing a null value. This means, if you use Option you eliminate null references from your code, if you use nullable reference types they are still there.

The question that should come to mind here is, what difference does it make if I use null or not? Let me show you by an example. Assume that you have a program that takes a user id, a string to be parsed as int, as input. It looks up the user in your database then, if a user is found, it looks up the user’s e-mail address and finally, if an e-mail address for the user could be found it sends a promotional e-mail to the user. Using nullable reference types you method signatures might look like this:

public Customer? GetCustomerById(int userId);
public Email? GetEmailForCustomer(Customer customer);
public void SendPromotionalEmail(Email email);

and to cover for invalid input and possible null values, the calling code needs to look something like this:

public void SendPromoToCustomer(string id)
{
  if (int.TryParse(id, out var userId)
  {
    var customer = GetCustomerById();
    if (customer == null)
      return;
    var email = GetEmailForCustomer(customer);
    if (email == null)
      return;
    SendPromotionalEmail(email);
  }
}

Now, this is a really simple example but it clearly shows the noise that the handling of null values introduces to the code. In a large codebase, null checks like these are everywhere.

Let me show you how the code would look using Option, Bind and Do from language-ext (here comes the part where the fact that Option is a monad makes all the difference). First, let’s change the null reference type returning methods to return Option instead:

public Option<Customer> GetCustomerById(int userId);
public Option<Email> GetEmailForCustomer(Customer customer);
public void SendPromotionalEmail(Email email);

Now, the calling code can be re-written to this:

public void SendPromoToCustomer(string id)
{
  parseInt(id)
    .Bind(GetCustomerById)
    .Bind(GetEmailForCustomer)
    .Do(SendPromotionalEmail);
}

This code still handles the possibility that the id string might not be possible to parse to an int, that GetCustomerById might not return a user, and that an e-mail address might be missing, but since we always have a valid instance to work with, an Option, we can just push it through the workflow and let the framework (language-ext) handle the error cases.

Bind and Do are extensions methods on Option. They inspect the content of the Option and if it contains a value the function given as parameter will be called with the value of the Option as a parameter. The return value of the function will then be used in the next call to Bind or Do. However, if the Option does not contain any value, the function will not be called. Instead, a new empty Option will be returned directly by Bind or Do.

Conclusion

I truly believe that Option is a better alternative to null for representing the abscence of a value. However, I also understand that using Option and language-ext is a big change which requires many developers to re-think and change the way they think and reason about handling these cases. Hence, I would not introduce this into a large existing codebase where developers are used to handling null. In that case I would recommend introducing nullable reference types, if possible (C# 8 is not supported on older frameworks).

But, starting a new, smaller, project I would opt for using language-ext and trying to get away from null as much as possible.

If you are interested in learning more, I recommend that you take a look at the book review that I wrote on the book ”Functional programming in C#”, https://ericbackhage.net/c/book-review-functional-programming-in-c/

Book review: Functional programming in C#

I have been interested in functional programming for quite some time, experimenting with both F# and Clojure as well as trying out some Scala. But I must admit that I have never really put full focus on it for any longer period, not until I found this book:

Book cover

Functional programming in C# – How to write better C# code is a book by Enrico Buonanno, a CS graduate from Columbia University with over 15 years of experience as a developer, architect, and trainer. It is published by Manning and you can find it at https://www.manning.com/books/functional-programming-in-c-sharp.

What made this book appealing was that it is written for developers who are fluent in C#, a language that I know quite well, meaning I did not have to struggle with both a new language and all the new concepts and ways to think that comes with functional programming. In retrospect this was a really nice way to learn what functional programming is all about.

This book is not for the beginner programmer. It goes into several advanced topics in depth and I would only recommend it to an experienced C# developer who is willing to spend a lot of time and energy on learning about functional concepts such as immutability, functors, monads, partial applicaction, currying and the likes.

With that said, if the above description fits you, I can really recommend that you give this book a try. It describes a lot of concepts and has a lot of sample code and coding excercises. The first eight chapters all ends with a bunch of challenging excercises for the reader and there is also a github repository available that includes the authors own functional library and excercises and solutions from the book. You can find the repository at https://github.com/la-yumba/functional-csharp-code.

The only critique I can give is that I think that it would have been better if the author used the well established language-ext library instead of developing his own. When comparing the two I get impression that the authors library is pretty much a subset of language-ext and does not add anything I don’t get from language-ext.

I feels nice to finally understand what a monad is. My next step on this journey will probably be to take a deep dive in a pure functional language (like Haskell).