Functional flavor in C# 7 with pattern matching
Posted on Sun 11 February 2018 in .NET
With many great features C# 7 comes with new and shiny pattern matching. I know that this feature is less usable then in other languages, but it add new possibilities to coding. In this article I want to share with you my experiments with monads.
Optional<T>
I believe that some of you know that null exception is big pain in developer life. I hear that even null creator say that this was a "billion-dollar mistake" to invent null. In other hand some languages like F# or Rust try to decrees of null using provide Optional monad. This monad have two possibilities happen:
- Some - indicate that we have some value,
- None - we do not have any value in this optional.
So I try to implement this using OOP in C# like that:
public interface IOptional<T> where T : class
{
T Value { get; }
}
public abstract class Optional<T> : IOptional<T>
where T : class
{
public abstract T Value { get; }
public static IOptional<T> Some(T value) => new Some<T>(value);
public static IOptional<T> None() => new None<T>();
}
public sealed class Some<T> : Optional<T>
where T : class
{
public override T Value { get; }
public Some(T value)
{
Value = value;
}
}
public sealed class None<T> : Optional<T>
where T : class
{
public override T Value => throw new NotSupportedException();
}
And also I implement some simple test:
class Person
{
public Person(string name, string surname)
{
Name = name;
Surname = surname;
}
public string Name { get; set; }
public string Surname { get; set; }
}
class Program
{
public static IOptional<Person> GetPerson(bool isSome)
=> isSome ?
Optional<Person>.Some(new Person("Adam", "Kowalski")) :
Optional<Person>.None();
static void Main(string[] args)
{
var opt = GetPerson(false);
switch (opt)
{
case Some<Person> p:
Console.WriteLine($"{p.Value.Name} {p.Value.Surname} is present");
break;
case None<Person> n:
Console.WriteLine("No one is present");
break;
}
}
}
I believe this simple implementation can be used to get rid of many nulls in code. Also when it will be use with new pattern matching feature as you see in example above. Maybe in next step I try to make some extension to LINQ to have possibility to use this with collections.
Result<E>
and Result<T, E>
Other monad I try to experiment with is Result. At first I meet this when I try to learn Rust programming language. What is Result then and how to use that in your code? So many functions take data and save it to database or filesystem. For most time this methods are void and if any problem occurs exception will be throw from it. On top of it we should catch that exception or leave it as it is and pray nothing will happen of have try and bunch of catch statements to have possibility to react of each individually or one catch statement to log and forget every exception. It's not like that I think that using exceptions in a bad thing. There are some places where exception are handy like validating in constructor in value objects but error handling could be done without of try catch hell. I implement two solutions one for void function and one for result with data:
public interface IResult<E>
{
E Error { get; }
}
public abstract class Result<E> : IResult<E>
{
public abstract E Error { get; }
public static IResult<E> Ok() => new Ok<E>();
public static IResult<E> Err(E error) => new Err<E>(error);
}
public sealed class Ok<E> : Result<E>
{
public override E Error => throw new NotSupportedException();
}
public sealed class Err<E> : Result<E>
{
public override E Error { get; }
public Err(E error)
{
Error = error;
}
}
public interface IResult<T, E>
{
T Outcome { get; }
E Error { get; }
}
public abstract class Result<T, E> : IResult<T, E>
{
public abstract T Outcome { get; }
public abstract E Error { get; }
public static IResult<T, E> Ok(T outcome) => new Ok<T, E>(outcome);
public static IResult<T, E> Err(E error) => new Err<T, E>(error);
}
public sealed class Ok<T, E> : Result<T, E>
{
public override T Outcome { get; }
public override E Error => throw new NotSupportedException();
public Ok(T outcome)
{
Outcome = outcome;
}
}
public sealed class Err<T, E> : Result<T, E>
{
public override T Outcome => throw new NotSupportedException();
public override E Error { get; }
public Err(E error)
{
Error = error;
}
}
Implementation Result
class Program
{
public static IResult<string> GetResult(bool isOk)
{
return isOk ?
Result<string>.Ok():
Result<string>.Err("Ooops we got error");
}
static void Main(string[] args)
{
var res = GetResult(false);
switch (res)
{
case Ok<string> ok:
Console.WriteLine("Everything is fine");
break;
case Err<string> err:
Console.WriteLine(err.Error);
break;
}
}
}
In other hand Result
class Program
{
public static IResult<int,string> GetResult(bool isOk)
{
return isOk ?
Result<int, string>.Ok(1):
Result<int, string>.Err("Ooops we got error");
}
static void Main(string[] args)
{
var res = GetResult(true);
switch (res)
{
case Ok<int, string> ok:
Console.WriteLine($"Lucky number is {ok.Outcome}");
break;
case Err<int, string> err:
Console.WriteLine(err.Error);
break;
}
}
}
One thing I cannot resolve that I can't hide Error in Ok response and Outcome in Error. I workaround that witch NotSupportedException, but maybe you can help me with that. At the end I want to give you Github Gist where all code with test programs are available:
If you also experiment with monads with C# 7 please share with me your experiences with that. As always I invite you to comments section to discuss article. See you next time!