Functional Programming in C# - Expressions, Option, Either
This article is on making C# code more functional by using functional abstractions. It has been preceeded by 2 articles on why I’ve got to here, and the background reasons behind trying functional programming in C#.
Part 1 summarised how I started in FP in C# by learning LINQ and trying Project Euler puzzles.
Part 2 is a detailed look at the first Euler puzzle with source for the first puzzle and source code for the next 17 puzzles using Imperative and LINQ
Part 3 is this article on making C# code more functional by using abstractions from the excellent functional c# library
Notes Are my developer notes whilst working through the book
Update 31st July 2012
Here is a series of articles I’m writing on building an application functionally
We are going to be looking at:
- Expressions / ternary operator
- Pure functions
- Immutable types
- Option type
- Match and Map
- Bind
- Either type
Inspiration for this article and where to practically start with FP in C#. The initial problems the C# functional library language-ext was trying to solve were that of Nulls and Immutability Original HN Article from 2014 on language-ext
If we remember that the core tenants of what FP are
- Functions first
- Immutability
Expressions first helps keep the functions small.
Immutability helps guide the functions towards being pure (no side effects)
Index of Examples
Source code for this article is here and here are the examples demonstrating FP abstractions:
- One(); // Pure functions
- Two(); // Immutability, Smart Constructor
- Three(); // Option type with None and Some
- Four(); // Option with Match to primitive
- Five(); // Option and Map (Functor)
- Six(); // IEnumerable and Select (Functor)
- Seven(); // Chaining Map
- Eight(); // Bind
- Nine(); // Bind chain
- Ten(); // IEnumerable and Extension methods chaining
- Eleven(); // Either - Exception
- Twelve(); // Either - Validation pipeline
Expressions
Try to make everything you do an expression, rather than a sequence of statements eg if using expression-bodied methods that start with =>
static void PrintHello() =>
Console.WriteLine("Hello World from a function");
Then prefer the conditional operator
aka ternary operators
as they are more concise leading to more readable code
static Option<string> GetValue(bool hasValue) =>
hasValue
? Some("Bob")
: None;
// Even more concise
static Option<string> GetValue(bool hasValue) => hasValue ? Some("Bob") : None;
What we are doing is building up a sequence of expressions which are then very easy to test and reason about.
Pure functions
Pure functions don’t refer to any global state. The same inputs will always get the same output. Combined with immutable data types this means you can be sure the same inputs will give the same outputs.
static void One()
{
// A function is pure if the same input always gives the same output
// can't have any IO...eg API call,
var result = Double(2);
// This is impure - as the website may be down, so a unit test would return a different result
var resultb = GetHtml("test.com");
}
// This is a pure function
// Very easy to unit test
static int Double(int i) => i * 2;
// Pure function - input (int) will always produce the same output (bool)
// as there is no shared state the function can be static
static bool IsPrime(int number)
{
if (number <= 1) return false;
if (number == 2) return true;
if (number % 2 == 0) return false;
for (long i = 2; i < number; i+=2)
if (number % i == 0)
return false;
return true;
}
If we have mutable objects it could be possible for another function to mutate the object we were working on concurrently.
So if we have a pure function (which doesn’t act on a mutable global variable) then we can make the function static
. More discussion in ch 2.2.3 of the orange book
Immutable Data objects / Smart Constructors
We never want to mutate an object in FP, but a create a new one. This makes sure there are no side effects caused somewhere else, thus ensuring a function remains pure. It also makes concurrency simpler.
// Immutability (Core part of FP!), Smart ctor
static void Two()
{
PersonOO dave = new PersonOO();
dave.Name = "dave";
dave.Age = 45;
// Don't do this in FP.. F# (by default), Haskell etc. does not allow this
// Should avoid mutating objects in place and favour making new ones (as can be sure no side effects anywhere else)
dave.Age = 46;
Person bob = new Person(name: "bob", age: 22);
//bob.Age = 23; // compiler doesn't allow this
// Creating a new version of bob with an updated age
var bob2 = bob.With(age: 43);
// A new version of bob with updated age
var bob3 = Birthday(bob);
}
// Function is pure - no side effects ie not updating anything which could be used anywhere else
// we are returning a brand new object
static Person Birthday(Person p) => p.With(age: 43);
class PersonOO
{
public string Name { get; set; }
public int Age { get; set; }
}
class Person
{
public string Name { get; } // C#6 Getter only auto-property
public int Age { get; }
// Smart constructor enforcing a name and age are given
public Person(string name, int age)
{
Name = name;
Age = age;
}
// Updates here, enforcing a new object is returned every time
// strings are nullable because they are reference types
public Person With(string name = null, int? age = null)
{
// if null, return the current value
// else set the newly passed in value
// ?? null coalescing operator
return new Person(name: name ?? Name, age: age ?? Age);
}
}
Interesting C#8 nullable / non-nullable reference types, same intent as Option<T>
discussed here
These act like record types or data types
in other languages. Essentially can only be a finitie set of attributes. good example using With
Why use the With to return a copy of an object when wanting to mutate it? Good SO Question here
Update 2020 - 24th Jul
I’m now using this concept a lot and generally have something like this:
public class RequestResult
{
public Uri Uri { get; }
public HttpStatusCode? HttpStatusCode { get; }
public TimeSpan? TimeSpan { get; } // when just created can be a null timespan, or a blank link
public Exception? Exception { get; } // can be the exception for the initial call, or 2nd getting the content
public string? Message { get; } // eg Invalid Scheme
public RequestResult(Uri uri, HttpStatusCode? httpStatusCode, TimeSpan? timeSpan = null, Exception? exception = null, string? message = null)
{
Uri = uri;
HttpStatusCode = httpStatusCode;
TimeSpan = timeSpan;
Exception = exception;
Message = message;
}
public RequestResult With(TimeSpan? timeSpan = null, Exception? exception = null, string? message = null) =>
new RequestResult(
Uri, // never change once created
HttpStatusCode, // never change once created
timeSpan ?? TimeSpan,
exception ?? Exception,
message ?? Message
);
public override string ToString() =>
$"requestResult: {Uri} {HttpStatusCode} " +
$"{(TimeSpan.HasValue ? TimeSpan.Value.TotalMilliseconds.ToString() : string.Empty)}ms " +
$"{Exception?.Message} {Exception?.InnerException?.Message} {Message}";
}
This will get much simpler in C#9 Records
Option type
Many functional languages disallow null values, as null-references can introduce hard to find bugs. Option is a type safe alternative to null values ref to a few words in this section. As discussed above C#8 is getting nullable and non-nullable reference types which should give similar safety.
Pattern: Whenever I’m using a primitive type eg int, string.
Nulls wont ever leave C#. LanguageExt was created to help avoid nulls by using an Option.
None / Some
An Option<T>
can be in one of two states. Some
representing the presence of a value and None
representing the lack of a value. Unlike null, an option type forces the user to check if a value is actually present, thereby mitigating many of the problems of null values.
// Option type with None and Some
static void Three()
{
Option<string> html = GetHtmlThree("badurl"); // will fail
Option<string> htmlb = GetHtmlThree("test.com"); // will succeed
}
static Option<string> GetHtmlThree(string url) =>
// None represents nothing. Part of language-ext
// Some represents a container for the string
url == "test.com" ? Some("html here") : None;
Match
// Option type with Match down to primitive type
static void Four()
{
// 2 different results of the GetHtml function
Option<string> result = None;
Option<string> resultb = Some("html here");
// Forces us to deal with the None case
// Null reference exceptions can't happen as it wont compile unless we handle it
// Match down to primitive type (as opposed to elevated types of Option<T>, IEnumerable<T>, Either<T>)
string stringResult = result.Match(Some: x => x, None: () => "none returned"); // none returned
string stringResultb = resultb.Match(Some: x => x, None: () => "none returned"); // html here
}
Map
We can Match
down to a primitive type, or can stay in the elevated types and do logic using Map
// Match and Map
static void Five()
{
string value = GetValue(true)
.Match(Some: name => $"Hello, {name}", None: () => "Goodbye");
Console.WriteLine(value); // Hello, Bob
// Map - Functor
// Lambda inside Map wont be invoked if Option is in None state
// Option is a replacement for if statements ie if obj == null
// Working in elevated context to do logic
// Similar to LINQ on IEnumerable.. ie if it is an empty list, then nothing will happen
Option<string> result = GetValue(true).Map(name => $"Hello, {name}");
Console.WriteLine(result); // Some(Hello, Bob)
Option<string> resultb = GetValue(false).Map(name => $"Hello, {name}"); // None
}
// Ternary nice and clean
static Option<string> GetValue(bool hasValue) =>
hasValue ? Some("Bob") : None;
Map and IEnumerable
Apply a function to the containers inner values, and take into account None. This is very similar to working with IEnumerable and running a Select
over each item in the collection.
// IEnumerable Map and Select
static void Six()
{
Option<string> name = Some("Bob");
// Map works on Option applying the function..just like select on IEnumerable.. they are both functors
Option<string> result = name.Map(x => x.ToUpper()); // Some(BOB)
IEnumerable<string> names = new[] { "Bob", "Alice" };
IEnumerable<string> resultb = names.Map(x => x.ToUpper()); // a normal IEnumerable
IEnumerable<string> resultc = names.Select(x => x.ToUpper()); // a normal IEnumerable
}
Option, Match, Map and Functors
Allow us to stay in the elevated context of core functional types such as Option<A>
, Either<L, R>
, Try<A>
etc.. We can consider IEnumerable<T>
to be a core functional type as it abstracts away null checking, and does not mutate (ie always returns a new result).
Good examples at bottom of message which are here too
IEnumerable
and Option
are Functors
. Essentially anything which has a reasonable implementation of Map is a functor.
GetValue below may, or may not return a string. The function signature is ‘honest’ in that it is obvious that it may or may not return a string as the return type is Option<string>
.
Match
brings down the elevated type. Essentially this is a null check.
The strategy is to try and work in the elevated level of abstraction to avoid: loops, null checks etc.. which are error prone. Why not abstract this away if we can?
Map chaining
Lets see another example of using Map
to stay in the elevated context and not have to worry about nulls. We want to apply multiple functions in the elevated context.
// Using Map (to avoid having to check for null at each stage)
static void Seven()
{
//Option<string> html = GetHtml("a");
Option<string> html = GetHtml("c");
// shorten html
Option<string> shortHtml = html.Map(x => x.Substring(0, 1)); // Some(a)
// put on https
Option<string> addedHttps = shortHtml.Map(x => $"https://{x}"); // Some(https://a)
// come down from elevated context and deal with None
string final = addedHttps.Match(Some: x => x, None: () => "No html returned");
}
static Option<string> GetHtml(string url)
{
if (url == "a") return "aa";
if (url == "b") return "bb";
return None;
}
Bind
Rather than having the do separate calls to Map, we can use Bind, which allows us to chain multiple functions together that all the return Option
. Bind is like Map
but can flatten two Option
Example here is using Bind to compose 2 functions which return an Option<string>
:
// Bind - Monad - Bind two Option<strings>
// SelectMany or FlatMap
static void Eight()
{
// If either GetFirstName() or MakeFullName(string firstName) returned None
// then name would be None
// return is not an Option<Option<string>>.. but flattened
Option<string> name = GetFirstName()
.Bind(MakeFullName);
Console.WriteLine(name); // Some(Joe Bloggs)
}
static Option<string> GetFirstName() =>
Some("Joe");
static Option<string> MakeFullName(string firstName) =>
Some($"{firstName} Bloggs");
Another example of Bind
on Option<string>
:
// Using Bind so we can chain multiple Option<T> functions together
static void Nine()
{
Option<string> html = GetHtml("a");
//Option<string> html = GetHtml("c");
// we don't want to have Option<Option<string>>
Option<string> shortHtml = html.Bind(x => ShortenHtml(x)); // x is a string
// put on https using shortened Method syntax so don't need x
Option<string> addedHttps = shortHtml.Bind(PutOnHttps);
Option<string> both = html.Bind(ShortenHtml)
.Bind(PutOnHttps);
// Come down from elevated context and deal with None
string final = addedHttps.Match(Some: x => x, None: () => "No html returned");
string finalb = both.Match(Some: x => x, None: () => "No html returned");
}
static Option<string> ShortenHtml(string html) =>
html == "" ? None :
Some(html.Substring(0, 10));
static Option<string> PutOnHttps(string html) =>
html.Length < 3 ? None : // business rule to catch invalid html
Some("https://" + html);
Collections / Method chaining in LINQ vs Bind
In my broken links checker example application, after getting the html from a page, I’m dealing with collections and have used extension method chaining.. is there a better way using Bind?
Here is some exploratory code showing that we can stay in the elevated context by using IEnumerable<T>
which handles nulls fine, and by using chained extension methods.
Bind is for working with Option<T>
and Either<L,R>
(and more) but also supports IEnumerable<T>
more information
// IEnumerable and Extension methods
static void Ten()
{
IEnumerable<string> listHrefs = GetListHrefs();
var baseUrl = "https://davemateer.com";
// LINQ extension method style
// if UrlType is Invalid (ie not Absolute or Relative) then don't add to this list
IEnumerable<string> finalUrl = listHrefs
// extension method which suffixes Relative, Absolute or Invalid
.GetUrlTypes()
// depending on UrlType above, process the url
.ProcessUrls(baseUrl);
}
static IEnumerable<string> ProcessUrls(this IEnumerable<string> urls, string baseUrl) =>
// ProcessUrl could return None so we use Bind instead of Select
urls.Bind(x => ProcessUrl(x, baseUrl));
static Option<string> ProcessUrl(string url, string baseUrl) =>
url.EndsWith("Absolute") ? url :
url.EndsWith("Relative") ? Some(baseUrl + url) :
None;
static IEnumerable<string> GetUrlTypes(this IEnumerable<string> html) =>
// Apply GetUrlType to each element in the list
html.Select(GetUrlType);
// Using expressions instead of if..return
static string GetUrlType(string link) =>
link.StartsWith("https://") ? $"{link} : Absolute" :
link.StartsWith("/") ? $"{link} : Relative" :
"{link} : Unknown";
static IEnumerable<string> GetListHrefs()
{
yield return "/programming-in-c-sharp";
yield return "https://bbc.co.uk";
yield return "mailto:davemateer@gmail.com";
}
Either - Exception handling
Here we are using Either
to return back the Exception
or the valid result html string
.
// Either - Exception handling
static void Eleven()
{
Option<string> result = GetHtml("invalidurl"); // None. ie it will swallow the exception
Either<Exception, string> resultb = GetHtmlE("invalidurl"); // Left(System.InvalidOperationException)
// Did the request throw an exception or return html?
resultb.Match(
Left: ex => HandleException(ex),
Right: ProcessPipeline
);
// Keep in the elevated context
var resultc = resultb.Map(x => x.Substring(0, 1)); // if not an Exception ie Right, then apply a lambda expression
// Bind an Either<Exception, string> with an Either<Exception, string>
var resultd = resultb.Bind(DoSomething);
}
static Either<Exception, string> DoSomething(string thing) => thing.Substring(0, 1);
static void HandleException(Exception ex) => Console.WriteLine(ex);
static void ProcessPipeline(string html) => Console.WriteLine(html);
public static Either<Exception, string> GetHtmlE(string url)
{
var httpClient = new HttpClient(new HttpClientHandler());
try
{
var httpResponseMessage = httpClient.GetAsync(url).Result;
return httpResponseMessage.Content.ReadAsStringAsync().Result;
}
catch (Exception ex) { return ex; }
}
Either - Error Handling (Validation or Exception)
Railway Oriented Programming
// Either - Errors in Validation handling
static void Twelve()
{
var url = "https://davemateer.com";
var result = RunUrlValidationPipeline(url);
var message = result.Match(Left: r => $"Rejected because: {r.ReasonForRejection}",
Right: x => "Success"); // Rejected because: Is not in allowed suffixes
}
// Railway oriented validation
// a url which needs to go through a validation pipeline
// if it fails at any point it goes on the left track
// and wont go any further on right
public static Either<URLRejection, string> RunUrlValidationPipeline(string url) =>
DoesUrlStartWithHttp(url)
.Bind(DoesUrlStartWithHttps)
.Bind(IsUrlInAllowedSuffixes);
public static Either<URLRejection, string> DoesUrlStartWithHttp(string url) => url; // pass
public static Either<URLRejection, string> DoesUrlStartWithHttps(string url) => url; // pass
public static Either<URLRejection, string> IsUrlInAllowedSuffixes(string url)
{
return new URLRejection { ReasonForRejection = "Is not in allowed suffixes" };
//return url; // hack for yes it does
}
public class URLRejection
{
public string ReasonForRejection { get; set; }
}
Either type - Further abstractions Exceptions
Try<A>
or an Exception<A>
are further abstractions over Either
.
Parameterised Unit Tests
As functions get smaller and are easier to test, it is very useful to be able to test a lot of scenarios using parameters. I’m using xunit
here:
using static Xunit.Assert;
// Parameterised Tests using Xunit
[Theory]
[InlineData("http://microsoft.com/", URLType.Absolute)]
[InlineData("http://microsoft.com", URLType.Absolute)]
[InlineData("https://microsoft.com/", URLType.Absolute)]
[InlineData("https://microsoft.com", URLType.Absolute)]
[InlineData("http://www.microsoft.com/", URLType.Absolute)]
[InlineData("http://www.microsoft.com", URLType.Absolute)]
[InlineData("https://www.microsoft.com/", URLType.Absolute)]
[InlineData("https://www.microsoft.com", URLType.Absolute)]
[InlineData("Https://www.microsoft.com", URLType.Absolute)]
// Site root relative
[InlineData("/", URLType.Relative)]
[InlineData("/2016/10/17/Blog-with-Jekyll-and-host-for-free", URLType.Relative)]
// Document relative - need to watch for these
//[InlineData("dreamweaver/contents.html", URLType.Relative)]
// Protocol relative - meaning current protocol ie http or https
//[InlineData("//www.adobe.com/support/dreamweaver/contents.html", URLType.Relative)]
[InlineData("//draft.blogger.com/rearrange?blogID=9185258468770746323&widgetType=Profile&widgetId=Profile1&action=editWidget§ionId=footer-2-2", URLType.Protocol)]
//https://stackoverflow.com/a/28865728/26086
[InlineData("spotify:track:5xRYEnLUno3P8LmAxjFuLg", URLType.Invalid)]
[InlineData("ftp://example.com/download.zip", URLType.Invalid)]
[InlineData("mailto:davemateer@gmail.com", URLType.Invalid)]
[InlineData("file:///home/user/file.txt", URLType.Invalid)]
[InlineData("tel:1-888-555-5555", URLType.Invalid)]
[InlineData("urn:isbn:0451450523", URLType.Invalid)]
[InlineData("view-source:https://stackoverflow.com/questions/176264/what-is-the-difference-between-a-uri-a-url-and-a-urn?rq=1", URLType.Invalid)]
[InlineData("#paragraph1", URLType.Invalid)]
[InlineData("#", URLType.Invalid)]
[InlineData("https://davemateer.com/2018/01/30/Cmder-Shell#aliases", URLType.Absolute)]
public static void GetURLType_Absolute_ReturnAbsolute(string input, URLType expected)
{
var result = input.GetURLType();
Equal(expected, result);
}
Summary
As louthy said in his post, it will take time to all sink in. It can take years to really master it. Most of the functionality in language-ext is there to help compose expressions.
These ‘basics’ of FP in C# allow us to build applications which have:
- Power (do more with less code, raise the level of abstraction)
- Safety (avoiding unnecessary side effects makes code easier to reason about)
- Clarity (we spend more time maintaining and consuming existing code than writing new. Achieving clarity will become more natural)
By having:
- Short methods which are ideally expressions ie just a return or =>
- Composed methods using LINQ for collections, Map and Bind for Option
, Either<L,R> - Static methods so no OO boilerplate
- Immutable objects so no side effects (making our lives easier as the only way something can change is in the function we are in)
With the techniques shown here, I’ve built an application which crawls for dead links on a website. I’ve used this application to be the real world test bed as I’ve gone thorugh learning FP. As in most real world applications, there is much more complexity than initially it looks like. These techniques have guided my code to be simpler and more maintainable.