Functional Thread 6 part 2: Exceptions

[)roi(]

Executive Member
Joined
Apr 15, 2005
Messages
5,859
#1
Previous Thread
In the last Functional thread I explored how FP uses the 1st class function abilities in C# and ended introducing some higher order functions:
  • Fold
  • Flatten
  • Map
For reference, you can find that thread here:
https://mybroadband.co.za/forum/threads/functional-thread-6-functions.980749/
Exception Handling
The purpose of this thread will be to focus on:
  • Exception handling in Functional Programming (FP) vs. the OOP approach.
  • Loose versus Tight coupling of input validations or any procedural data verification process
I'll also be creating 3 FP data types that are more typically used with exception handling namely:
  • Maybe
  • Either
  • Validation

Target Audience
This thread is targeted at intermediate developers; hence I won't be going into a lot of step by step detail. I will also assume that you are familiar with standard OOP Exception Handling;
  • Try, Catch, Finally blocks
  • Throwing of exceptions
  • etc.

Goals
  • I do not intend to not walk through a step by step discussion about the creation of these data types; however I will provide some comments with respect to certain design choices.
  • The primary goal is rather to spend more time demonstrating how this is addressed by FP.
Note:
This is going to be a very code heavy thread.
 
Last edited:

[)roi(]

Executive Member
Joined
Apr 15, 2005
Messages
5,859
#2
Maybe Type
The Maybe type which is also known as the Option or Optional is used to represent computations which might fail and hence not return a value. Simplistically the Maybe type can also be substituted for class instance objects that may or may not be initialised i.e. has a pointer to a valid instance on the heap, or a null pointer.

In C# as of C# 2.0; the Nullable type was introduced to supposedly better work with type that could be null; however Nullable only currently supports value types; not class types (that btw will be finally added in C# 8.0); however the biggest issue that I have with Nullable is that it doesn't really make it easier to work with nullable types; yes we can use the ? inline operator to more safely access the internals without if else block; but even with that it's quite limited.

In FP, the Maybe type fulfils the same purpose; and here's a base implementation:
C#:
  internal enum MaybeState {
    Empty, Some
  }

  public struct Maybe<S> {
    internal readonly S some;
    internal readonly MaybeState state;

    public bool IsSome => state == MaybeState.Some;
    public bool IsEmpty => state == MaybeState.Empty;

    internal Maybe(S some) {
      this.state = some == null ? MaybeState.Empty : MaybeState.Some;
      this.some = some == null ? default(S) : some;
    }

    private Maybe(MaybeState state) {
      this.state = MaybeState.Empty;
      this.some = default(S);
    }

    public static Maybe<S> Some(S some) {
      return new Maybe<S>(some);
    }

    public static Maybe<S> Empty() {
      return new Maybe<S>(MaybeState.Empty);
    }

    public override string ToString() {
      return string.Format("Maybe: [{0}: {1}]", state, IsEmpty ? "Empty" : this.Fold(s => s.ToString()));
    }
  }
Basically it's a type that can either have some value or it's empty; synonymous with null. It unlike Nullable can work with any type, including class types.

Base example usage
Here's an example of using Maybe for int (value type) and string (class type).
C#:
// Variables that contain some element
var name1 = Maybe<string>.some("Jack");
var age1 = Maybe<int>.some(23);

// Variables that are empty i.e. contain nothing; similar to null.
var name2 = Maybe<string>.empty();
var age2 = Maybe<int>.empty();
However the base code by itself; is just as limited as the Nullable type; so to make it more useful we need to add on some functionality; more specifically FP algebras like Fold, Map, etc.
 

[)roi(]

Executive Member
Joined
Apr 15, 2005
Messages
5,859
#3
Making our Maybe type comply with FP algebras
In typical C# style we'll tag on functionality to an existing type by using static extension methods; in the same that it's done with Linq.

Fold
C#:
public static S2 Fold<S1, S2>(this Maybe<S1> e, Func<S1, S2> someFn) {
  switch (e.state) {
    case MaybeState.Some:
      return someFn(e.some);
    default:
      return default(S2);
  }
}
Fold allows us to apply a reducing function to the element stored in the Maybe type; essentially allowing us in its most simplistic use to extract the value; whereas if its empty; the default value for that type will be returned.

Next let's add Map (functor), FlatMap (monad), Apply (applicative functor)


Map
C#:
public static Maybe<S2> Map<S1, S2>(this Func<S1, S2> fn, Maybe<S1> e) {
  return e.Map(fn);
}

public static Maybe<S2> Map<S1, S2>(this Maybe<S1> e, Func<S1, S2> fn) {
  switch (e.state) {
    case MaybeState.Some:
      return Maybe<S2>.Some(fn(e.some));
    default:
      return Maybe<S2>.Empty();
    }
}

FlatMap
C#:
public static Maybe<S2> FlatMap<S1, S2>(this Maybe<S1> e, Func<S1, Maybe<S2>> fn) {
  switch (e.state) {
    case MaybeState.Some:
      return fn(e.some);
    default:
      return Maybe<S2>.Empty();
   }
}

public static Maybe<S2> FlatMap<S1, S2>(this Func<S1, Maybe<S2>> fn, Maybe<S1> e) {
  return e.FlatMap(fn);
}

Apply
C#:
public static Maybe<S2> Apply<S1, S2>(this Maybe<S1> e, Maybe<Func<S1, S2>> fn) {
   return fn.FlatMap(g => e.Map(x => g(x)));
}

public static Maybe<S2> Apply<S1, S2>(this Maybe<Func<S1, S2>> fn, Maybe<S1> e) {
   return e.Apply(fn);
}

public static Maybe<S> ToMaybe<S>(this S s) {
  return Maybe<S>.Some(s);
}
However as I'll demonstrate later Apply on its own can be quite verbose when it comes to working with types that have a lot of parameter values in the constructor method, so to streamline that with be adding some Applicative Functor helper static methods for arities from 1 to 10; more can be added later if needed.

LiftA (Lift function and input into the Applicative algebra)
C#:
public static Maybe<S> LiftA<A, S>(this Func<A, S> fn, Maybe<A> a) {
  return fn.Map(a);
}

public static Maybe<S> LiftA<A, B, S>(this Func<A, B, S> fn, Maybe<A> a, Maybe<B> b) {
  return fn.Curry().Map(a).Apply(b);
}

public static Maybe<S> LiftA<A, B, C, S>(this Func<A, B, C, S> fn, Maybe<A> a, Maybe<B> b, Maybe<C> c) {
  return fn.Curry().Map(a).Apply(b).Apply(c);
}

public static Maybe<S> LiftA<A, B, C, D, S>(this Func<A, B, C, D, S> fn, Maybe<A> a, Maybe<B> b, Maybe<C> c, Maybe<D> d) {
  return fn.Curry().Map(a).Apply(b).Apply(c).Apply(d);
}

public static Maybe<S> LiftA<A, B, C, D, E, S>(this Func<A, B, C, D, E, S> fn, Maybe<A> a, Maybe<B> b, Maybe<C> c, Maybe<D> d, Maybe<E> e) {
  return fn.Curry().Map(a).Apply(b).Apply(c).Apply(d).Apply(e);
}

public static Maybe<S> LiftA<A, B, C, D, E, F, S>(this Func<A, B, C, D, E, F, S> fn, Maybe<A> a, Maybe<B> b, Maybe<C> c, Maybe<D> d, Maybe<E> e, Maybe<F> f) {
  return fn.Curry().Map(a).Apply(b).Apply(c).Apply(d).Apply(e).Apply(f);
}

public static Maybe<S> LiftA<A, B, C, D, E, F, G, S>(this Func<A, B, C, D, E, F, G, S> fn, Maybe<A> a, Maybe<B> b, Maybe<C> c, Maybe<D> d, Maybe<E> e, Maybe<F> f, Maybe<G> g) {
  return fn.Curry().Map(a).Apply(b).Apply(c).Apply(d).Apply(e).Apply(f).Apply(g);
}

public static Maybe<S> LiftA<A, B, C, D, E, F, G, H, S>(this Func<A, B, C, D, E, F, G, H, S> fn, Maybe<A> a, Maybe<B> b, Maybe<C> c, Maybe<D> d, Maybe<E> e, Maybe<F> f, Maybe<G> g, Maybe<H> h) {
  return fn.Curry().Map(a).Apply(b).Apply(c).Apply(d).Apply(e).Apply(f).Apply(g).Apply(h);
}

public static Maybe<S> LiftA<A, B, C, D, E, F, G, H, I, S>(this Func<A, B, C, D, E, F, G, H, I, S> fn, Maybe<A> a, Maybe<B> b, Maybe<C> c, Maybe<D> d, Maybe<E> e, Maybe<F> f, Maybe<G> g, Maybe<H> h, Maybe<I> i) {
  return fn.Curry().Map(a).Apply(b).Apply(c).Apply(d).Apply(e).Apply(f).Apply(g).Apply(h).Apply(i);
}

public static Maybe<S> LiftA<A, B, C, D, E, F, G, H, I, J, S>(this Func<A, B, C, D, E, F, G, H, I, J, S> fn, Maybe<A> a, Maybe<B> b, Maybe<C> c, Maybe<D> d, Maybe<E> e, Maybe<F> f, Maybe<G> g, Maybe<H> h, Maybe<I> i, Maybe<J> j) {
  return fn.Curry().Map(a).Apply(b).Apply(c).Apply(d).Apply(e).Apply(f).Apply(g).Apply(h).Apply(i).Apply(j);
}

Match
And now let's add a helper static method called Match that will allow us to execute different Action closures for a Maybe with Some value or when it's Empty.
C#:
public static void Match<S>(this Maybe<S> e, Action empty, Action<S> some) {
  switch (e.state) {
    case MaybeState.Some:
      some(e.some);
      return;
    default:
      empty();
      return;
  }
}

Print
And finally let's for console debugging purposes add an easy way to print what's in our Maybe type.
C#:
public static void Print<S>(this Maybe<S> e, string title = "") {
  Console.WriteLine("{0} ---> Maybe[{1}]", title, e.IsEmpty ? "Empty" : e.Fold(s => string.Format("Some: {0}", s)));
}
 
Last edited:

[)roi(]

Executive Member
Joined
Apr 15, 2005
Messages
5,859
#4
Maybe usage examples
Ok so that's the definition of our Maybe type; now let's get to the more interesting part; and start to use some of this functionality in code.
C#:
var age = Maybe<int>.Some(23);

// simplistic calculation of year of birth.
var year = age.Map(v => 2019 - v);

// let's print that out
age.Print("Birth year"); // Birth year ---> Maybe[Some: 1996]
Using maybe to validate input
Let's say we have a form that users need to complete; containing a Firstname, Surname and Email
More typically we'd need to perform some validation on their entries to determine if the values provided are acceptable.


Person class
First off let's define a Person class to contain these values.
C#:
public sealed class Person {
  public readonly string Firstname;
  public readonly string Surname;
  public readonly string Email;

  public Person(string firstname, string surname, string email) {
   if (!(firstname.Length > 5)) || (!(surname.Length > 4)) || (!email.Contains("@")) { errors.Add("bad email"); }
     throw new Exception("Person Validation Failed");
   }
   Firstname = firstname;
   Surname = surname;
   Email = email;
  }

  public override string ToString() {
    return string.Format("Person: [Firstname: {0}, Surname: {1}, Email: {2}]", Firstname, Surname, Email);
  }
}
In the above definition we have added our validation code to the constructor method; and if there's a mistake we throw an Exception. This is not consider a good way to do this because there is a tight coupling of the validation and the Person class. What if we needed to use this Person class somewhere else; but without a different set of validation criteria?

In OOP we could create another closure that first validates the code and then returns that an instance of Person if its ok, or throw an exception if it not. Naturally calling this code would require us to wrap it in a try catch block, and similarly follow this pattern up the call stack.


Ok so how would FP do this?
First off let redefine Person and remove the tight coupling, and then add a Create helper function to gain access to a lambda function for instantiation.
C#:
public sealed class Person {
  public readonly string Firstname;
  public readonly string Surname;
  public readonly string Email;

  private Person(string firstname, string surname, string email) {
    Firstname = firstname;
    Surname = surname;
    Email = email;
  }

  public static Func<string, string, string, Person> Create = (firstname, surname, email) => {
    return new Person(firstname, surname, email);
  };

  public override string ToString() {
    return string.Format("Person: [Firstname: {0}, Surname: {1}, Email: {2}]", Firstname, Surname, Email);
  }
}
Ok so let's see this in use.
C#:
// These 3 variables simulate the return values from the user input
string fname = "john";
string sname = "parker";
string email = "john.parkeremail.com";

// Here in a single step we validate the input values, and then return an
// empty Maybe if the validation fails (which it does re fname and email)
Person.Create.LiftA(
  fname.Length > 5 ? Maybe<string>.Some(fname) : Maybe<string>.Empty(),
  sname.Length > 5 ? Maybe<string>.Some(sname) : Maybe<string>.Empty(),
  email.Contains("@") ? Maybe<string>.Some(email) : Maybe<string>.Empty()
).Print("Failure"); // Maybe Input Form Failure ---> Maybe[Empty]
Here's an example of a successful validation:
C#:
// These 3 variables simulate the return values from the user input
string fname = "johnathan";
string sname = "parker";
string email = "john.parker@email.com";

// Here in a single step we validate the input values, and then return an
// empty Maybe if the validation fails (which it does re fname and email)
Person.Create.LiftA(
  fname.Length > 5 ? Maybe<string>.Some(fname) : Maybe<string>.Empty(),
  sname.Length > 5 ? Maybe<string>.Some(sname) : Maybe<string>.Empty(),
  email.Contains("@") ? Maybe<string>.Some(email) : Maybe<string>.Empty()
).Print("Maybe Input Form Success");

// Success ---> Maybe[Some: Person: [Firstname: johnathan, Surname: parker, Email: john.parker@email.com]]
Naturally the validation code can be replaced by functions, and we can also easily .dot chain more operations like Map, FlatMap etc to transform the data in situ.

That all looks ok, but usually with an input form it's not just ok to know that it failed validation; you want to know what failed specifically and provide that feedback to the user. Unfortunately Maybe only allows us to store Some value or Empty; meaning its not going to work for us here.

To solve this we'll need another type; namely Either
 
Last edited:

[)roi(]

Executive Member
Joined
Apr 15, 2005
Messages
5,859
#5
Btw if you look at this line of code Person.Create.LiftA( and wondered how the Create function had a LiftA method, then this post will clear that up.

Person.Create is of course a lambda function and is defined in the Person class, for example:
C#:
public sealed class Person {
  ...
  public static Func<string, string, string, Person> Create = (firstname, surname, email) => {
    return new Person(firstname, surname, email);
  };
...
}
What should be confusing is that by default the C# Func delegate type doesn't not have a LiftA method, to make that work we had to add some static helper methods to the Func delegate type, this btw was done as part of the Maybe type definition, for example:
C#:
public static Maybe<S> LiftA<A, S>(this Func<A, S> fn, Maybe<A> a) {
  return fn.Map(a);
}
...and as I mentioned we added this helper methods for arities from 1 to 10. You should not that this static method is actually extending the Func delegate type, where it's used in conjunction with the Maybe type. Naturally when we define the Either type; we'll need to duplicate this, except then for the Either type.

Note:
This code duplication is unfortunately not avoidable in C#, because C# does not have Higher Kinded Type polymorphism; otherwise called "generics on generics"; meaning that with HKTs we would be able to not only generically extrapolate code over the element type stored in our Maybe type, but also generically specify the container type.

The LiftA extension method's only purpose is to simplify the code we write, and the API for using it, for example the following code is the extended form of Applicative processing without the LiftA extension method vs the simplified version using LiftA.
C#:
// extended form of applicative processing
Person.Create.Curry()
           .Map(fname.Length > 5 ? Maybe<string>.Some(fname) : Maybe<string>.Empty())
           .Apply(sname.Length > 5 ? Maybe<string>.Some(sname) : Maybe<string>.Empty())
           .Apply(email.Contains("@") ? Maybe<string>.Some(email) : Maybe<string>.Empty())
           .Print("Maybe Input Form Success");

// LiftA simplified form of applicative processing
Person.Create.LiftA(
  fname.Length > 5 ? Maybe<string>.Some(fname) : Maybe<string>.Empty(),
  sname.Length > 5 ? Maybe<string>.Some(sname) : Maybe<string>.Empty(),
  email.Contains("@") ? Maybe<string>.Some(email) : Maybe<string>.Empty()
).Print("Maybe Input Form Success");
 
Last edited:

[)roi(]

Executive Member
Joined
Apr 15, 2005
Messages
5,859
#6
Either Type
The Either type represents a sum type with two stored possibilities; for example a typeEither<string, Person>, would either return a string instance or a Person instance.


Ok let's define Either
C#:
internal enum EitherState {
  Left, Right
}

public struct Either<L, R> {
  internal readonly L left;
  internal readonly R right;
  internal readonly EitherState state;

  public bool isRight => state == EitherState.Right;
  public bool isLeft => state == EitherState.Left;

  internal Either(R right) {
    this.state = EitherState.Right;
    this.right = right;
    this.left = default(L);
  }

  internal Either(L left) {
    this.state = EitherState.Left;
    this.right = default(R);
    this.left = left;
  }

  public static Either<L, R> Right(R right) {
    return new Either<L, R>(right);
  }

  public static Either<L, R> Left(L left) {
    return new Either<L, R>(left);
  }

  public override string ToString() {
    return string.Format("Either: [{0}: {1}]", this.state, this.Fold<L, R, string>(s => s.ToString(), r => r.ToString()));
  }
}

Let's define the FP Algebra conformances & static helper methods (same as Maybe)
C#:
#region Either - Functional Extension Methods
public static partial class ƒ {

  #region Either - Fold
  public static R2 Fold<L, R1, R2>(this Either<L, R1> e, Func<L, R2> leftFn, Func<R1, R2> rightFn) {
    switch (e.state) {
      case EitherState.Right:
        return rightFn(e.right);
      default:
        return leftFn(e.left);
    }
  }
  #endregion

  #region Either - Functor
  public static Either<L, R2> MapR<L, R1, R2>(this Func<R1, R2> fn, Either<L, R1> e) {
    return e.MapR(fn);
  }

  public static Either<L, R2> MapR<L, R1, R2>(this Either<L, R1> e, Func<R1, R2> fn) {
    switch (e.state) {
      case EitherState.Right:
        return Either<L, R2>.Right(fn(e.right));
      default:
        return Either<L, R2>.Left(e.left);
    }
  }

  public static Either<L2, R> MapL<L1, L2, R>(this Func<L1, L2> fn, Either<L1, R> e) {
    return e.MapL(fn);
  }

  public static Either<L2, R> MapL<L1, L2, R>(this Either<L1, R> e, Func<L1, L2> fn) {
    switch (e.state) {
      case EitherState.Right:
        return Either<L2, R>.Right(e.right);
      default:
        return Either<L2, R>.Left(fn(e.left));
    }
  }
  #endregion

  #region Either - Monad
  public static Either<L, R2> FlatMapR<L, R1, R2>(this Either<L, R1> e, Func<R1, Either<L, R2>> fn) {
    switch (e.state) {
      case EitherState.Right:
        return fn(e.right);
      default:
        return Either<L, R2>.Left(e.left);
    }
  }

  public static Either<L, R2> FlatMapR<L, R1, R2>(this Func<R1, Either<L, R2>> fn, Either<L, R1> e) {
    return e.FlatMapR(fn);
  }
  #endregion

  #region Either - Applicative Functor
  public static Either<L, R2> Apply<L, R1, R2>(this Either<L, R1> e, Either<L, Func<R1, R2>> fn) {
    return fn.FlatMapR(g => e.MapR(x => g(x)));
  }

  public static Either<L, R2> Apply<L, R1, R2>(this Either<L, Func<R1, R2>> fn, Either<L, R1> e) {
    return e.Apply(fn);
  }

  public static Either<L, R> ToEither<L, R>(this R r) {
    return Either<L, R>.Right(r);
  }
  #endregion

  #region Either - Applicative Functor - Lift a function & actions
  public static Either<L, R> LiftA<A, L, R>(this Func<A, R> fn, Either<L, A> a) {
    return fn.MapR(a);
  }

  public static Either<L, R> LiftA<A, B, L, R>(this Func<A, B, R> fn, Either<L, A> a, Either<L, B> b) {
    return fn.Curry().MapR(a).Apply(b);
  }

  public static Either<L, R> LiftA<A, B, C, L, R>(this Func<A, B, C, R> fn, Either<L, A> a, Either<L, B> b, Either<L, C> c) {
    return fn.Curry().MapR(a).Apply(b).Apply(c);
  }

  public static Either<L, R> LiftA<A, B, C, D, L, R>(this Func<A, B, C, D, R> fn, Either<L, A> a, Either<L, B> b, Either<L, C> c, Either<L, D> d) {
    return fn.Curry().MapR(a).Apply(b).Apply(c).Apply(d);
  }

  public static Either<L, R> LiftA<A, B, C, D, E, L, R>(this Func<A, B, C, D, E, R> fn, Either<L, A> a, Either<L, B> b, Either<L, C> c, Either<L, D> d, Either<L, E> e) {
    return fn.Curry().MapR(a).Apply(b).Apply(c).Apply(d).Apply(e);
  }

  public static Either<L, R> LiftA<A, B, C, D, E, F, L, R>(this Func<A, B, C, D, E, F, R> fn, Either<L, A> a, Either<L, B> b, Either<L, C> c, Either<L, D> d, Either<L, E> e, Either<L, F> f) {
    return fn.Curry().MapR(a).Apply(b).Apply(c).Apply(d).Apply(e).Apply(f);
  }

  public static Either<L, R> LiftA<A, B, C, D, E, F, G, L, R>(this Func<A, B, C, D, E, F, G, R> fn, Either<L, A> a, Either<L, B> b, Either<L, C> c, Either<L, D> d, Either<L, E> e, Either<L, F> f, Either<L, G> g) {
    return fn.Curry().MapR(a).Apply(b).Apply(c).Apply(d).Apply(e).Apply(f).Apply(g);
  }

  public static Either<L, R> LiftA<A, B, C, D, E, F, G, H, L, R>(this Func<A, B, C, D, E, F, G, H, R> fn, Either<L, A> a, Either<L, B> b, Either<L, C> c, Either<L, D> d, Either<L, E> e, Either<L, F> f, Either<L, G> g, Either<L, H> h) {
    return fn.Curry().MapR(a).Apply(b).Apply(c).Apply(d).Apply(e).Apply(f).Apply(g).Apply(h);
  }

  public static Either<L, R> LiftA<A, B, C, D, E, F, G, H, I, L, R>(this Func<A, B, C, D, E, F, G, H, I, R> fn, Either<L, A> a, Either<L, B> b, Either<L, C> c, Either<L, D> d, Either<L, E> e, Either<L, F> f, Either<L, G> g, Either<L, H> h, Either<L, I> i) {
    return fn.Curry().ToEither<L, Func<A, Func<B, Func<C, Func<D, Func<E, Func<F, Func<G, Func<H, Func<I, R>>>>>>>>>>().Apply(a).Apply(b).Apply(c).Apply(d).Apply(e).Apply(f).Apply(g).Apply(h).Apply(i);
  }

  public static Either<L, R> LiftA<A, B, C, D, E, F, G, H, I, J, L, R>(this Func<A, B, C, D, E, F, G, H, I, J, R> fn, Either<L, A> a, Either<L, B> b, Either<L, C> c, Either<L, D> d, Either<L, E> e, Either<L, F> f, Either<L, G> g, Either<L, H> h, Either<L, I> i, Either<L, J> j) {
    return fn.Curry().MapR(a).Apply(b).Apply(c).Apply(d).Apply(e).Apply(f).Apply(g).Apply(h).Apply(i).Apply(j);
  }
  #endregion

  #region Either - Match
  public static void Match<L, R>(this Either<L, R> e, Action<L> left, Action<R> right) {
    switch (e.state) {
      case EitherState.Right:
        right(e.right);
        return;
      default:
        left(e.left);
        return;
    }
  }
  #endregion

  #region Print
  public static void Print<L, R>(this Either<L, R> e, string title = "") {
    Console.WriteLine("{0} ---> Either[{1}]", title, e.Fold(l => string.Format("Left: {0}", l), r => string.Format("Right: {0}", r)));
  }
  #endregion

}
#endregion
As I warned, lot's of code; but some of it should look quite similar to Maybe. Next let's relook at our validation example, but this time using the Either type instead of Maybe.

C#:
// These 3 variables simulate the return values from the user input
string fname = "john";
string sname = "parker";
string email = "john.parkeremail.com";

Person.Create.LiftA(
  fname.Length > 5 ? Either<string, string>.Right(fname) : Either<string, string>.Left("Invalid Firstname"),
  sname.Length > 5 ? Either<string, string>.Right(sname) : Either<string, string>.Left("Invalid Surname"),
  email.Contains("@") ? Either<string, string>.Right(email) : Either<string, string>.Left("Invalid Email")
).Print("Failure"); // Failure ---> Either[Left: Invalid Firstname]
As you can hopefully see our input has 2 errors; Firstname length, and Email missing the @ sign. Using Either however only show us the first failure; this is because the Either design like Maybe is monadic; and monadic processes always stop at the first point of failure.

On a validated input form the user would be notified of their 1st error only, once they fix that and resubmit they'll find out about their Email error, and so on....

Which means that although Either is an improvement over Maybe; it is not the perfect solution for validation type processing, but it is good for processing when 1 process is dependant on the success completion of another; a good example of this is SQL transaction processing. I'll provide an extensive example of that later, but let's first look at how we capture all the errors at the same time, in order to provide a single consolidated feedback to the user.

...and if you guessed we'd need another type; then well done; YES we need a type called Validation, which is a type very similar to Either, except that it allows us to capture all the errors by replacing the monadic limitation of Either with a process that enables concurrency.
 
Last edited:

[)roi(]

Executive Member
Joined
Apr 15, 2005
Messages
5,859
#7
Validation Type
A data-type like Either but with an accumulating Applicative process. I'll point out the code difference that enables concurrency i.e. where it differs to Either's monadic process.

Ok let's define Validation
C#:
internal enum ValidationState {
  Left, Right
}

public struct Validation<L, R> {
  internal readonly List<L> left;
  internal readonly R right;
  internal readonly ValidationState state;

  public bool isRight => state == ValidationState.Right;
  public bool isLeft => state == ValidationState.Left;

  internal Validation(R right) {
    this.state = ValidationState.Right;
    this.right = right;
    this.left = new List<L> { };
  }

  internal Validation(L left) {
    this.state = ValidationState.Left;
    this.right = default(R);
    this.left = new List<L> { left };
  }

  internal Validation(List<L> left) {
    this.state = ValidationState.Left;
    this.right = default(R);
    this.left = left;
  }

  public static Validation<L, R> Right(R right) {
    return new Validation<L, R>(right);
  }

  public static Validation<L, R> Left(L left) {
    return new Validation<L, R>(left);
  }

  public static Validation<L, R> Left(List<L> left) {
    return new Validation<L, R>(left);
  }

  public override string ToString() {
    return string.Format("Validation: [{0}: {1}]", this.state, this.Fold(s => s.ToString(), r => r.ToString()));
  }
}
Let's define the FP Algebra conformances & static helper methods (similar to Either)
C#:
#region Validation - Functional Extension Methods
public static partial class ƒ {

  #region Validation - Fold
  public static R2 Fold<L, R1, R2>(this Validation<L, R1> e, Func<List<L>, R2> l, Func<R1, R2> r) {
    switch (e.state) {
      case ValidationState.Right:
        return r(e.right);
      default:
        return l(e.left);
    }
  }
  #endregion

  #region Validation - Functor
  public static Validation<L, R2> MapR<L, R1, R2>(this Func<R1, R2> fn, Validation<L, R1> e) {
    return e.MapR(fn);
  }

  public static Validation<L, R2> MapR<L, R1, R2>(this Validation<L, R1> e, Func<R1, R2> fn) {
    switch (e.state) {
      case ValidationState.Right:
        return Validation<L, R2>.Right(fn(e.right));
      default:
        return Validation<L, R2>.Left(e.left);
    }
  }
  #endregion

  #region Validation - Monad
  public static Validation<L, R2> FlatMap<L, R1, R2>(this Validation<L, R1> e, Func<R1, Validation<L, R2>> fn) {
    switch (e.state) {
      case ValidationState.Right:
        return fn(e.right);
      default:
        return Validation<L, R2>.Left(e.left);
    }
  }

  public static Validation<L, R2> FlatMap<L, R1, R2>(this Func<R1, Validation<L, R2>> fn, Validation<L, R1> e) {
    return e.FlatMap(fn);
  }
  #endregion

  #region Validation - Applicative Functor
  public static Validation<L, R2> Apply<L, R1, R2>(this Validation<L, R1> e, Validation<L, Func<R1, R2>> fn) {
    switch ((a: fn.isRight, b: e.isRight)) {
      case var t when t.And(true, true):
        return fn.FlatMap(g => e.MapR(x => g(x)));
      case var t when t.And(false, false):
        fn.left.AddRange(e.left);
        return Validation<L, R2>.Left(fn.left);
      case var t when t.First(false):
        return Validation<L, R2>.Left(fn.left);
      default:
        return Validation<L, R2>.Left(e.left);
    }
  }

  public static Validation<L, R2> Apply<L, R1, R2>(this Validation<L, Func<R1, R2>> fn, Validation<L, R1> e) {
    return e.Apply(fn);
  }

  public static Validation<L, R> ToValidation<L, R>(this R r) {
    return Validation<L, R>.Right(r);
  }
  #endregion

  #region Validation - Applicative Functor - Lift a function & actions
  public static Validation<L, R> LiftA<A, L, R>(this Func<A, R> fn, Validation<L, A> a) {
    return fn.MapR(a);
  }

  public static Validation<L, R> LiftA<A, B, L, R>(this Func<A, B, R> fn, Validation<L, A> a, Validation<L, B> b) {
    return fn.Curry().MapR(a).Apply(b);
  }

  public static Validation<L, R> LiftA<A, B, C, L, R>(this Func<A, B, C, R> fn, Validation<L, A> a, Validation<L, B> b, Validation<L, C> c) {
    return fn.Curry().MapR(a).Apply(b).Apply(c);
  }

  public static Validation<L, R> LiftA<A, B, C, D, L, R>(this Func<A, B, C, D, R> fn, Validation<L, A> a, Validation<L, B> b, Validation<L, C> c, Validation<L, D> d) {
    return fn.Curry().MapR(a).Apply(b).Apply(c).Apply(d);
  }

  public static Validation<L, R> LiftA<A, B, C, D, E, L, R>(this Func<A, B, C, D, E, R> fn, Validation<L, A> a, Validation<L, B> b, Validation<L, C> c, Validation<L, D> d, Validation<L, E> e) {
    return fn.Curry().MapR(a).Apply(b).Apply(c).Apply(d).Apply(e);
  }

  public static Validation<L, R> LiftA<A, B, C, D, E, F, L, R>(this Func<A, B, C, D, E, F, R> fn, Validation<L, A> a, Validation<L, B> b, Validation<L, C> c, Validation<L, D> d, Validation<L, E> e, Validation<L, F> f) {
    return fn.Curry().MapR(a).Apply(b).Apply(c).Apply(d).Apply(e).Apply(f);
  }

  public static Validation<L, R> LiftA<A, B, C, D, E, F, G, L, R>(this Func<A, B, C, D, E, F, G, R> fn, Validation<L, A> a, Validation<L, B> b, Validation<L, C> c, Validation<L, D> d, Validation<L, E> e, Validation<L, F> f, Validation<L, G> g) {
    return fn.Curry().MapR(a).Apply(b).Apply(c).Apply(d).Apply(e).Apply(f).Apply(g);
  }

  public static Validation<L, R> LiftA<A, B, C, D, E, F, G, H, L, R>(this Func<A, B, C, D, E, F, G, H, R> fn, Validation<L, A> a, Validation<L, B> b, Validation<L, C> c, Validation<L, D> d, Validation<L, E> e, Validation<L, F> f, Validation<L, G> g, Validation<L, H> h) {
    return fn.Curry().MapR(a).Apply(b).Apply(c).Apply(d).Apply(e).Apply(f).Apply(g).Apply(h);
  }

  public static Validation<L, R> LiftA<A, B, C, D, E, F, G, H, I, L, R>(this Func<A, B, C, D, E, F, G, H, I, R> fn, Validation<L, A> a, Validation<L, B> b, Validation<L, C> c, Validation<L, D> d, Validation<L, E> e, Validation<L, F> f, Validation<L, G> g, Validation<L, H> h, Validation<L, I> i) {
    return fn.Curry().ToValidation<L, Func<A, Func<B, Func<C, Func<D, Func<E, Func<F, Func<G, Func<H, Func<I, R>>>>>>>>>>().Apply(a).Apply(b).Apply(c).Apply(d).Apply(e).Apply(f).Apply(g).Apply(h).Apply(i);
  }

  public static Validation<L, R> LiftA<A, B, C, D, E, F, G, H, I, J, L, R>(this Func<A, B, C, D, E, F, G, H, I, J, R> fn, Validation<L, A> a, Validation<L, B> b, Validation<L, C> c, Validation<L, D> d, Validation<L, E> e, Validation<L, F> f, Validation<L, G> g, Validation<L, H> h, Validation<L, I> i, Validation<L, J> j) {
    return fn.Curry().ToValidation<L, Func<A, Func<B, Func<C, Func<D, Func<E, Func<F, Func<G, Func<H, Func<I, Func<J, R>>>>>>>>>>>().Apply(a).Apply(b).Apply(c).Apply(d).Apply(e).Apply(f).Apply(g).Apply(h).Apply(i).Apply(j);
  }
  #endregion

  #region Validation - Match
  public static void Match<L, R>(this Validation<L, R> e, Action<List<L>> left, Action<R> right) {
    switch (e.state) {
      case ValidationState.Right:
        right(e.right);
        return;
      default:
        left(e.left);
        return;
    }
  }
  #endregion

  #region Print
  public static void Print<L, R>(this Validation<L, R> e, string title = "") {
    Console.WriteLine("{0} ---> Validation[{1}]", title, e.Fold(
      l => string.Format("Left: '{0}'", l.Fold("", (ai, ei) => ai + ei + ",").DropLast(1)),
      r => string.Format("Right: {0}", r)
    ));
  }
  #endregion

}
#endregion
 

[)roi(]

Executive Member
Joined
Apr 15, 2005
Messages
5,859
#8
The difference that enables concurrency is this code:
C#:
  public static Validation<L, R2> Apply<L, R1, R2>(this Validation<L, R1> e, Validation<L, Func<R1, R2>> fn) {
    switch ((a: fn.isRight, b: e.isRight)) {
      case var t when t.And(true, true):
        return fn.FlatMap(g => e.MapR(x => g(x)));
      case var t when t.And(false, false):
        fn.left.AddRange(e.left);
        return Validation<L, R2>.Left(fn.left);
      case var t when t.First(false):
        return Validation<L, R2>.Left(fn.left);
      default:
        return Validation<L, R2>.Left(e.left);
    }
  }
The above code no longer uses FlatMap; which is the monadic process, basically we replaced the code following this paragraph with the above switch statement that allows us to evaluate the left hand side (LHS) and right hand side (RHS) of the applicative process; this ability to evaluate more than 1 thing at a time is what enables concurrency. i.e. all validation branches effectively execute together.

Here's the same method in Either using FlatMap
C#:
  public static Either<L, R2> Apply<L, R1, R2>(this Either<L, R1> e, Either<L, Func<R1, R2>> fn) {
    return fn.FlatMapR(g => e.MapR(x => g(x)));
  }

Logic Helper Methods (And & First)
In the above Validation I use a And to aid in evaluating LHS versus RHS of an applicative process. Here's the code for that.
C#:
public static partial class ƒ {
  public static bool And<A, B>(this (A, B) tuple, A a, B b) where A : IEquatable<A> where B : IEquatable<B> {
    return tuple.Item1.Equals(a) && tuple.Item2.Equals(b);
  }

  public static bool Or<A, B>(this (A, B) tuple, A a, B b) where A : IEquatable<A> where B : IEquatable<B> {
    return tuple.Item1.Equals(a) || tuple.Item2.Equals(b);
  }

  public static bool First<A, B>(this (A, B) tuple, A a) where A : IEquatable<A> where B : IEquatable<B> {
    return tuple.Item1.Equals(a);
  }

  public static bool Second<A, B>(this (A, B) tuple, B b) where A : IEquatable<A> where B : IEquatable<B> {
    return tuple.Item2.Equals(b);
  }
}
...and hopefully this helps explains a bit of the seemingly magic behind this; in the switch statement we are evaluating a Tuple of the LHS and RHS, here's that code:
C#:
  switch ((a: fn.isRight, b: e.isRight)) {
    ....
  }
...and the And helper methods allow use to conjoin the expect output for use in the case statement, for example this check is both sides have values stored in the right branch of the Validation sum type (meaning both were a success).

The only other difference in the Validation type relates to the definition of the left property (field); it's a List<L> instead of just a generic L that the Either type has.

Next let's relook at our validation example, but this time using the Validation type instead of Either
C#:
// These 3 variables simulate the return values from the user input
string fname = "john";
string sname = "parker";
string email = "john.parkeremail.com";

Person.Create.LiftA(
  fname.Length > 5 ? Validation<string, string>.Right(fname) : Validation<string, string>.Left("Invalid Firstname"),
  sname.Length > 5 ? Validation<string, string>.Right(sname) : Validation<string, string>.Left("Invalid Surname"),
  email.Contains("@") ? Validation<string, string>.Right(email) : Validation<string, string>.Left("Invalid Email")
).Print("Failure"); // Failure ---> Validation[Left: 'Invalid Firstname,Invalid Email']
Hopefully that has provided you with some appreciation for the design differences between these 3 FP types; and some indication of which one to pick depending on your use case.

Next post in this thread
The next post in this thread will be looking at a more complex use case for Either's monadic processing; more specifically with regards to SQL transaction queries; which have many sub processes that could either fail or succeed. This will demonstrate how we can use the monadic power of the Either type to not only bullet proof exception handling, but also to fairly significantly reduce the amount of code we write around SQL processing, including providing a less verbose API.

Note:
If you have any questions, please post them in this thread; similarly if you need me to explain any part of this code in more detail. On completion of this thread I'll provide a complete project that you can play around with covering all this code and more.
 
Last edited:

[)roi(]

Executive Member
Joined
Apr 15, 2005
Messages
5,859
#9
OOP SQLite Code Example
The following code and examples are provided for comparison; on how a database is setup, initialised and how queries are executed including instantiating element class instances for each record in the result set.

Aside from a code review; this part is to help establish a basic for comparison with the FP code that will be posted after this. What's important to note is that exception handling takes the more traditional OOP try catch form, in that errors encountered are re-thrown in order to pass up this up the stack to initiating call. Naturally all functions that could throw exceptions are wrapped in a try catch block at the point of call; and for simplicity sake; we simply output the final errors to the console.


Student Class & Gender Enum
C#:
public sealed class Student {
  public readonly int Id;
  public readonly string Name;
  public readonly string Surname;
  public readonly Gender Gender;
  public readonly DateTime Dob;
  public int Age {
    get { return Dob.DurationInYears(to: DateTime.Today); }
  }

  private Student(int id, string name, string surname, Gender gender, DateTime dob) {
    Id = id;
    Name = name;
    Surname = surname;
    Gender = gender;
    Dob = dob;
  }

  public static Func<int, string, string, Gender, DateTime, Student> Create = (id, name, surname, gender, dob) => {
    return new Student(id, name, surname, gender, dob);
  };

  public static Func<IDataReader, Student> FromSQLReader = reader => {
    return Create(reader.ToInt("id"), reader.ToString("name"), reader.ToString("surname"), GenderConvert.CharToGender(Convert.ToString(reader["gender"])), reader.toDateTime("dob"));
  };

  public override string ToString() {
    return string.Format("Student: [Id: {0}, Name: {1}, Surname: {2}, Gender: {3}, Dob: {4}]", Id, Name, Surname, Gender, Dob);
  }
}

public enum Gender {
  Male, Female, Unknown
}

public static class GenderConvert {
  public static Gender CharToGender(string character) {
    switch (character) {
      case "M": return Gender.Male;
      case "F": return Gender.Female;
      default: return Gender.Unknown;
    }
  }
}

SQLite Config
Simple static variable for database file path.
C#:
public static class Config {
  public static string dbFilePath = "Test.db";
  public static string dbConnection = string.Format("URI=file:{0}", Config.dbFilePath);
}

SQLite Scripts
SQL statements to create Student table, insert some records, and finally 2 select queries.
C#:
public static class Script {
  public static class Select {
    public static readonly string sqlGetStudentsById = @"
SELECT *
FROM Students
WHERE id = @StudentID;
    ".Trim();

    public static readonly string sqlGetAllStudents = @"
SELECT *
FROM Students;
    ".Trim();
  }

  public static class Create {
    public static string students = @"
CREATE TABLE IF NOT EXISTS Students(
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
surname TEXT NOT NULL,
gender CHAR(1) NULL,
dob INTEGER NULL
);
    ".Trim();
  }

  public static class Insert {
    public static string students = @"
INSERT INTO Students (name, surname, gender, dob) VALUES('Johnny','Walker','M', 1062540000);
INSERT INTO Students (name, surname, gender, dob) VALUES('Lucas','Radebe','M', 1031004000);
INSERT INTO Students (name, surname, gender, dob) VALUES('Charles','Glass','M', 1062540000);
INSERT INTO Students (name, surname, gender, dob) VALUES('Sally','Williams','F', 1031004000);
INSERT INTO Students (name, surname, gender, dob) VALUES('Mary','Smith','F',  1062540000);
INSERT INTO Students (name, surname, gender, dob) VALUES('Jenny','Lopez','F', 1062540000);
INSERT INTO Students (name, surname, gender, dob) VALUES('Daniel','Steele','M', 1031004000);
   ".Trim();
  }

  public static string initialise = new List<string> { Create.students, Insert.students }.Fold("", (a, e) => a + e + "\n");
}

OOP SQLite Setup
Let's start with a short review of the code needed to create a SQLite database, create tables, inserting records and convert DateTime.
C#:
public static partial class ƒ {
  public static bool DeleteFileIfExists_OOP(this string filePath) {
    var result = false;
    if (File.Exists(filePath)) {
      File.Delete(filePath);
      result = true;
    }
    return result;
  }

  public static DateTime UnixTimeSecondsToDateTime(this long from) {
    return DateTimeOffset.FromUnixTimeSeconds(from).DateTime.ToLocalTime();
  }

  public static DateTime toDateTime(this IDataReader record, string sqlColumnName) {
    return ((long)Convert.ToInt32(record[sqlColumnName])).UnixTimeSecondsToDateTime();
  }
}

public static class SQL {
  public static bool CreateSQLiteDB_OOP(string dbFilePath) {
    try {
      dbFilePath.DeleteFileIfExists_OOP();
      SQLiteConnection.CreateFile(dbFilePath);
    } catch (Exception e) {
      throw new Exception(string.Format("message: {0}\ntrace: {1}\n", e.Message, e.StackTrace));
    }
    return true;
  }
}

OOP SQLite Queries
Examples of executing a Select query; 1 without parameters and another with a parameter. Including executing these questions against SQLite.
C#:
class MainClass {
  public static List<User> GetUsers_OOP() {
    var users = new List<User>();
    try {
      using (SQLiteConnection connection = new SQLiteConnection(Config.dbConnection)) {
        connection.Open();
        using (SQLiteCommand command = new SQLiteCommand(Script.Select.sqlGetUsers, connection)) {
          using (SQLiteDataReader reader = command.ExecuteReader()) {
            while (reader.Read()) {
              users.Add(User.FromSQLReader(reader));
            }
          }
        }
        connection.Close();
      }
    } catch (Exception e) {
      throw new Exception(string.Format("message: {0}\ntrace: {1}\n", e.Message, e.StackTrace));
    }
    return users;
  }

  public static List<Student> GetStudentById_OOP(int studentId) {
    var students = new List<Student>();
    try {
      using (SQLiteConnection connection = new SQLiteConnection(Config.dbConnection)) {
        connection.Open();
        using (SQLiteCommand command = new SQLiteCommand(Script.Select.sqlGetStudentsById, connection)) {
          command.Parameters.AddWithValue("@StudentID", studentId);
          using (SQLiteDataReader reader = command.ExecuteReader()) {
            while (reader.Read()) {
              students.Add(Student.FromSQLReader(reader));
            }
          }
        }
        connection.Close();
      }
    } catch (Exception e) {
      throw new Exception(string.Format("message: {0}\ntrace: {1}\n", e.Message, e.StackTrace));
    }
    return students;
  }

  public static void Main(string[] args) {
    Console.WriteLine("Initialise_OOP");
    var result = false;
    try {
      SQL.CreateSQLiteDB_OOP(Config.dbFilePath);
      using (SQLiteConnection connection = new SQLiteConnection(Config.dbConnection)) {
        connection.Open();
        using (SQLiteCommand command = new SQLiteCommand(connection)) {
          command.CommandText = Script.initialise;
          command.ExecuteNonQuery();
        }
        connection.Close();
        result = true;
      }
    } catch (Exception e) {
      Console.WriteLine(e.Message);
    }

    if (result) {
      Console.WriteLine("GetAllStudents_OOP");
      result = false;
      try {
        GetAllStudents_OOP().ForEach(Console.WriteLine);
        result = true;
      } catch (Exception e) {
        Console.WriteLine(e.Message);
      }
    }

    if (result) {
      Console.WriteLine("GetStudentById_OOP");
      result = false;
      try {
        GetStudentById_OOP(2).ForEach(Console.WriteLine);
        result = true;
      } catch (Exception e) {
        Console.WriteLine(e.Message);
      }
    }
  }
}

OOP SQLite Console Output
C#:
Initialise_OOP
GetAllStudents_OOP
Student: [Id: 1, Name: Johnny, Surname: Walker, Gender: Male, Dob: 2003/09/03 12:00:00 AM]
Student: [Id: 2, Name: Lucas, Surname: Radebe, Gender: Male, Dob: 2002/09/03 12:00:00 AM]
Student: [Id: 3, Name: Charles, Surname: Glass, Gender: Male, Dob: 2003/09/03 12:00:00 AM]
Student: [Id: 4, Name: Sally, Surname: Williams, Gender: Female, Dob: 2002/09/03 12:00:00 AM]
Student: [Id: 5, Name: Mary, Surname: Smith, Gender: Female, Dob: 2003/09/03 12:00:00 AM]
Student: [Id: 6, Name: Jenny, Surname: Lopez, Gender: Female, Dob: 2003/09/03 12:00:00 AM]
Student: [Id: 7, Name: Daniel, Surname: Steele, Gender: Male, Dob: 2002/09/03 12:00:00 AM]
GetStudentById_OOP
Student: [Id: 2, Name: Lucas, Surname: Radebe, Gender: Male, Dob: 2002/09/03 12:00:00 AM]
 

[)roi(]

Executive Member
Joined
Apr 15, 2005
Messages
5,859
#10
Exception Handling
The C# language's exception handling is similar to many of the OOP centric languages; basically exceptions are thrown and safely managed by the try, catch, and finally keywords:
  • Try, code that may not succeed,
  • Catch, and handle failures
  • Finally, clean up resources.

Unchecked Exceptions
C# Exceptions are by design not checked, which implies that the function signature cannot syntactically specify what exceptions that could be thrown, neither does it ensure that the calling code is handling all the potential exceptions that could be thrown.

Note:
I don't consider checked exceptions to be a solution to this; because they just create new set of problems, that are IMO worse than the problems of unchecked exceptions.

Sum Type
Another way to look at functions that can throw exceptions is to compare then to a function with a sum type output; one which can be either a success or a failure.

Depending on how exceptions are thrown and how they are treated afterward, exceptions can either be exceedingly useful, or terribly annoying, and concurrency just makes that more complex.


What should we use Exceptions for?
Let's review the following approach with exceptions:
C#:
public static User validate(string username, string password) {
  if (!UserExists(username)) {
    throw new InvalidUserException();
  }
  if (!IsValid(username, password)) {
    throw new InvalidCredentialsException();
  }
  return User.Authenticated(username);
}
Depending on your point of view these types of uses of exceptions may be deemed to be a bit of code smell, because these are not the typical type of issues we would typically associate with an exception.

In comparisons neither of the following exceptions:
  • FileNotFoundException
  • IndexOutOfRangeException
...would be quite so easy to recover from in program flow. We could using this line of thought surmise that Exceptions should not be thrown for outcomes that are easy to quantify, but if that were at all true, then what should be done about the atypical error paths?

Note:
In traditional imperative code functions typically return a return code which would allow the subsequent code to evaluate whether it succeeded or failed. With C# OOP we have 2 more typical mechanisms that are used:
  • a syntactically specified return type specified in the function's signature
  • ...and unchecked exceptions.
 
Last edited:

[)roi(]

Executive Member
Joined
Apr 15, 2005
Messages
5,859
#11
So how does FP tackle this?
  1. A monadic sum type; a container type that conforms to Monad algebra
  2. Secondly we design our functions to behave more "purely", meaning the internals are based solely on the input parameters, and the function always return a result wrapped in a container type that is also monadic.
Note:
  • Monad is just the mathematical term for an abstract algebra that behaves somewhat similar to an if else logical code branch, which ensures that code dependent on the success of a previous computation will not be executed when there are prior failures.
  • Another name for monadic exception handling is railway oriented programming.
Monads guide you through the happy path -- Erik Meijer

FP approach to dissecting the SQLite problem:
First thing we do; is to dissect the OOP code; basically we want to separate the different sets of activities, in terms of a lego piece perspective. Naturally to build a FP API for this, means that we need to consider all the different sets of SQLite commands, and how these would fit together as a set of lego blocks that can be used to construct queries like OOP, without unnecessarily sacrificing the flexibility of the OOP API.

FP Code Subdivision.png
  1. Set-up the connection, open it and creating a command.
  2. After reviewing the API we discover that the SQL statement can be specified separately from the constructor method; why we'd want to do this may not seem apparent initially.
    • The reason why we do this is to ensure that our API will easily be able to switch between standard SQL statements and Stored Procedures.
  3. SQL action; separated because there's a few alternative actions like:
    • ExecuteScalar
    • ExecuteNonQuery
  4. This is a loop that steps through the result recordset and transforms it to a List of a specified type.
  5. Closing the connection and disposing command & connection.
Note:
The other thing to note is which commands can throw exceptions; FP can't avoid exceptions because that's the way the OOP API is designed; so out FP functions will have to catch an exceptions and transform this to our preferred way of wrapping success vs failures outcome.
 

[)roi(]

Executive Member
Joined
Apr 15, 2005
Messages
5,859
#12
OK let's build the FP API
The API will consist of a number of functions; that will enable architected composition; where certain pieces fit together; and other parts that can be more flexibly composed. Naturally this will be a bit of code; but with FP the terseness wins are always realised in large in a project's codebase.

Note:
The monadic container type that we'll be using is the Either because it's a flexible sum type with two outcomes and it has the monadic conformity that we'll require.
Let's building the components for containing exceptions, and rewrapping these as a Either type:
C#:
namespace FPIntro {
  public static partial class ƒ {
    public static string LogDefault(Exception e) {
      return string.Format("message: {0}\ntrace: {1}\n", e.Message, e.StackTrace);
    }

    public static Either<string, T> TryEitherLog<T>(Func<Either<string, T>> tryFunc) {
      try {
        return tryFunc();
      } catch (Exception e) {
        return Either<string, T>.Left(LogDefault(e));
      }
    }
  }
}
Above we have a LogDefault function, that we used to specify what information we want to collect when Exceptions occur; naturally we'd adjust this as required. Second function is a higher order function that safely runs a unary Function in a try catch block; either returning the success outcome as an Either<string, T>, or a failure that wraps the failure in the left branch of the Either<string, T> type.

Why we do this; may not be immediately evident; the purpose is primarily to ensure consistently in catching Exceptions from the OOP API, and standardising on the information we collect with these Exceptions. A side benefit is that we'll win on lines of code in our implementation, as you hopefully see in due course.

Let's now build our code to create and initialise a new database
C#:
namespace FPIntro {
  public static partial class ƒ {
    public static Either<string, bool> DeleteFileIfExists(this string filePath) {
      return TryEitherLog(() => {
        var result = false;
        if (File.Exists(filePath)) {
          File.Delete(filePath);
          result = true;
        }
        return Either<string, bool>.Right(result);
      });
    }
  }
}

namespace FPIntro {
  public static class SQL {
    #region New SQLite Database Instance
    public static Either<string, bool> CreateSQLiteDB(string dbFilePath) {
      return ƒ.TryEitherLog(() => {
        dbFilePath.DeleteFileIfExists().Match(
          right: (success) => { }, // NOP
          left: (message) => { throw new Exception(message); }
        );
        SQLiteConnection.CreateFile(dbFilePath);
        return Either<string, bool>.Right(true);
      });
    }
    #endregion
  }
}
Here you get to see the first use of our TryEitherLog higher order function; this wrapping function will catch any Exceptions including the one we throw if there a problem deleting an existing file.

Now let's build the bits that correspond to the number on the code tagged image in the previous post.

Number 1: The connection
C#:
namespace FPIntro {
  using DBInstance = Tuple<IDbConnection, IDbCommand>;
  public static class SQL {
    public static Either<string, DBInstance> ConnectToSQLite(string connectionString) {
      return ƒ.TryEitherLog(() => {
        var connection = new SQLiteConnection(connectionString);
        connection.Open();
        var command = connection.CreateCommand();
        return Either<string, DBInstance>.Right(new DBInstance(connection, command));
      });
    }
  }
}
Nothing too weird; we wrap everything in the TryEitherLog block to catch any Exceptions, and as expected we create a connection, then open it, and finally create a command. At this point we have two instances; one for the connection and another for the command; so our return needs to encapsulate both of these; because we'd want to be able to add stuff to the command, and finally we'd want to close the connection and depose both of these instances.

Which means our T in the Either<string, T> return type, needs to encapsulate both of the instance. The easiest way to do this is to use Tuples; and to make it even less verbose, I'll be using a using statement which essentially allows us to create a typealias / typedef for our tuple of these 2 instance types. i.e.
C#:
using DBInstance = Tuple<IDbConnection, IDbCommand>;
 
Last edited:

[)roi(]

Executive Member
Joined
Apr 15, 2005
Messages
5,859
#13
Number 2: The binding of SQL or Stored Procedure
C#:
namespace FPIntro {
  using DBInstance = Tuple<IDbConnection, IDbCommand>;
  public static class SQL {
    #region Bind SQL or Stored Procedure
    public static Func<DBInstance, Either<string, DBInstance>> BindSQL(string script) {
      return db => {
        return ƒ.TryEitherLog(() => {
          var (connection, command) = db;
          command.CommandText = script;
          return Either<string, DBInstance>.Right(new DBInstance(connection, command));
        });
      };
    }

    public static Func<DBInstance, Either<string, DBInstance>> BindProcedure(string script) {
      return db => {
        return ƒ.TryEitherLog(() => {
          var (connection, command) = db;
          command.CommandText = script;
          command.CommandType = CommandType.StoredProcedure;
          return Either<string, DBInstance>.Right(new DBInstance(connection, command));
        });
      };
    }
    #endregion
  }
}
Here we have to functions that can interchangeably used to either bind standard SQL statements, or a Stored Procedure. The db lambda variable in the above code is the incoming Tuple of DBInstance that is relayed from the previous monadic function to this one. You should also note the output type signature for this function; this format of receiving the unwrapped value from a previous function, and the returning an Either container type is the standard signature for a Monadic process. The Monad FlatMap method performs the unwrapping for us behind the scenes.

Number 3: The SQL Actions
For completeness I'll provide functions for the various actions.
C#:
namespace FPIntro {
  using DBInstance = Tuple<IDbConnection, IDbCommand>;
  public static class SQL {
    public static Func<DBInstance, Either<string, int>> ExecuteNonQuery() {
      return db => {
        return ƒ.TryEitherLog(() => {
          var (connection, command) = db;
          var result = command.ExecuteNonQuery();
          db.DisposeAndClose();
          return Either<string, int>.Right(result);
        });
      };
    }

    public static Func<DBInstance, Either<string, List<T>>> ExecuteReader<T>(Func<IDataReader, T> transform) {
      return db => {
        return ƒ.TryEitherLog(() => {
          var (connection, command) = db;
          var reader = command.ExecuteReader();
          var result = reader.ToType(transform);
          reader.Dispose();
          db.DisposeAndClose();
          return Either<string, List<T>>.Right(result);
        });
      };
    }

    public static Func<DBInstance, Either<string, T>> ExecuteScalar<T>(Func<object, T> transform) {
      return db => {
        return ƒ.TryEitherLog(() => {
          var (connection, command) = db;
          var result = transform(command.ExecuteScalar());
          db.DisposeAndClose();
          return Either <string, T>.Right(result);
        });
      };
    }
    #endregion
  }
}
WRT the above 3 functions; ExecuteNonQuery runs a statement or procedure and returns a int value representing a count of the actions completed. For both ExecuteReader and ExecuteScalar we have a higher order function that requires a function to transform the query result.

Number 4 and 5: Loop method to transform recordset, and DisposeAndClose
With ExecuteReader we have an additional internal static extension method called ToType that manages the transformation which as you can see below is a higher order function that iterates over the result recordset and converts this to a List using the provide transform function. We also have a static extension method called DisposeAndClose; which as you can see below, simply takes the instances for connection and command; and closes and disposes both of these.
C#:
namespace FPIntro {
  using DBInstance = Tuple<IDbConnection, IDbCommand>;
  public static class SQL {
    #region SqlExtension Methods
    public static void DisposeAndClose(this DBInstance db) {
      var (connection, command) = db;
      command.Dispose();
      connection.Close();
    }

    public static List<T> ToType<T>(this IDataReader reader, Func<IDataReader, T> fn) {
      var result = new List<T>();
      while (reader.Read()) {
        result.Add(fn(reader));
      }
      return result;
    }
    #endregion
  }
}
There are potentially a few other things we would still need to do to complete our FP API, for example supporting SQL Parameters:
C#:
namespace FPIntro {
  using DBInstance = Tuple<IDbConnection, IDbCommand>;
  public static class SQL {
    #region Sql Parameters tied to Values
    public static Func<DBInstance, Either<string, DBInstance>> Parameters(Action<IDataParameterCollection> fn) {
      return (db) => {
        return ƒ.TryEitherLog(() => {
          var (connection, command) = db;
          fn(command.Parameters);
          return Either<string, DBInstance>.Right(new DBInstance(connection, command));
        });
      };
    }
    #endregion
  }
}
 
Last edited:

[)roi(]

Executive Member
Joined
Apr 15, 2005
Messages
5,859
#14
Ok let's now compare the typical OOP code versus the FP code that uses our new API:

OOP: standard SQL query: GetAllStudents
C#:
public static List<Student> GetAllStudents_OOP() {
  var students = new List<Student>();
  try {
    using (SQLiteConnection connection = new SQLiteConnection(Config.dbConnection)) {
      connection.Open();
      using (SQLiteCommand command = new SQLiteCommand(Script.Select.sqlGetAllStudents, connection)) {
        using (SQLiteDataReader reader = command.ExecuteReader()) {
          while (reader.Read()) {
            students.Add(Student.FromSQLReader(reader));
          }
        }
      }
      connection.Close();
    }
  } catch (Exception e) {
    throw new Exception(string.Format("message: {0}\ntrace: {1}\n", e.Message, e.StackTrace));
  }
  return students;
}

// Usage Example:
try {
  GetAllStudents_OOP().ForEach(Console.WriteLine);
} catch (Exception e) {
  Console.WriteLine(e.Message);
}

FP: standard SQL query: GetAllStudents
C#:
public static Either<string, List<Student>> GetAllStudents_FP() {
  return SQL.ConnectToSQLite(Config.dbConnection)
            .Bind(SQL.BindSQL(script: Script.Select.sqlGetAllStudents))
            .Bind(SQL.ExecuteReader(transform: Student.FromSQLReader));
}

// Usage Example:
GetAllStudents_FP().Match(
  left: Console.WriteLine,
  right: xs => xs.ForEach(Console.WriteLine)
);
Bind btw just calls FlatMap; the name Bind is a bit more friendlier and descriptive than FlatMap; an alternative is Chain.
C#:
public static Either<L, R2> Bind<L, R1, R2>(this Either<L, R1> e, Func<R1, Either<L, R2>> fn) {
  return e.FlatMapR(fn);
}
 
Last edited:

[)roi(]

Executive Member
Joined
Apr 15, 2005
Messages
5,859
#15
Let's end this off with a comparison of a SQL query with a SQL Parameter:

OOP: SQL query with a parameter: GetAllStudentsById
C#:
public static List<Student> GetStudentById_OOP(int studentId) {
  var students = new List<Student>();
  try {
    using (SQLiteConnection connection = new SQLiteConnection(Config.dbConnection)) {
      connection.Open();
      using (SQLiteCommand command = new SQLiteCommand(Script.Select.sqlGetStudentsById, connection)) {
        command.Parameters.AddWithValue("@StudentID", studentId);
        using (SQLiteDataReader reader = command.ExecuteReader()) {
          while (reader.Read()) {
            students.Add(Student.FromSQLReader(reader));
          }
        }
      }
      connection.Close();
    }
  } catch (Exception e) {
    throw new Exception(string.Format("message: {0}\ntrace: {1}\n", e.Message, e.StackTrace));
  }
  return students;
}

// Usage Example:
try {
  GetStudentById_OOP(2).ForEach(Console.WriteLine);
} catch (Exception e) {
  Console.WriteLine(e.Message);
}

FP: SQL query with a parameter: GetAllStudentsById
C#:
public static Either<string, List<Student>> GetStudentById_FP(int studentId) {
  return SQL.ConnectToSQLite(Config.dbConnection)
            .Bind(SQL.Parameters(p => p.Add(new SQLiteParameter("@StudentID", studentId))))
            .Bind(SQL.BindSQL(script: Script.Select.sqlGetStudentsById))
            .Bind(SQL.ExecuteReader(transform: Student.FromSQLReader));
}

// Usage Example:
GetStudentById_FP(2).Match(
  left: Console.WriteLine,
  right: xs => xs.ForEach(Console.WriteLine)
);
 
Last edited:

[)roi(]

Executive Member
Joined
Apr 15, 2005
Messages
5,859
#16
Bonus:
What do you think it will take to make our FP API support SQL Server connections as opposed to SQLite?

C#:
public static Either<string, DBInstance> ConnectToSqlClient(string connectionString) {
  return ƒ.TryEitherLog(() => {
    var connection = new SqlConnection(connectionString);
    connection.Open();
    var command = connection.CreateCommand();
    return Either<string, DBInstance>.Right(new DBInstance(connection, command));
  });
}
That's it, we just need a new function to open the connection to a SQLConnection instance instead of SQLiteConnection, the rest of the code works the same without any modification.

Anyway hope you liked it; that it makes you to look at your code in a different way, and to help appreciate why most FP codebases are far smaller than their OOP counterparts.

Full Visual Studio Project
I have a bit of cleanup to do with the project that includes all this code and far more FP stuff;
I'll update this thread with a link to the full project for download, once I've finished cleaning it up a bit.
 
Last edited:

[)roi(]

Executive Member
Joined
Apr 15, 2005
Messages
5,859
#17
Extra Bonus
For anybody concerned about the potential overhead of these FP containers vs. native code; let me try to allay some of your scepticism by sharing another example which is included in the project.

Linq was added by Microsoft to enhance the power of working with datasets in a similar style to how SQL enhances the power of database queries. To follow along with that theme, Microsoft even chose to model Linq's syntax on that of SQL.

The specific example we'll explore is Linq's ability to like SQL Join two or more datasets based on matching keys (property fields). Here's a quick example of that:

First off we need some data
C#:
var students = new List<Student> {
  Student.Create(1, "Jack", "Sprat", Gender.Male, new DateTime(1990, 12, 1)),
  Student.Create(2, "Mary", "Contrary", Gender.Female, new DateTime(1992, 2, 13)),
  Student.Create(3, "Snow", "White", Gender.Female, new DateTime(1994, 6, 7))
};

var grades = new List<Grade> {
  Grade.Create(201801, 1 ,3, 75),
  Grade.Create(201801, 1, 4, 82),
  Grade.Create(201801, 2, 3, 93),
  Grade.Create(201801, 2, 1, 78),
  Grade.Create(201801, 3, 3, 73),
  Grade.Create(201801, 3, 2, 67)
};

var subjects = new List<Subject> {
  Subject.Create(1, "Biology"),
  Subject.Create(2, "Economics"),
  Subject.Create(3, "Mathematics"),
  Subject.Create(4, "Science")
};
Next let's review the Linq code to join these 3 datasets
C#:
 students
  .Join(inner: grades, outerKeySelector: a => a.Id, innerKeySelector: b => b.StudentId, resultSelector: (a, b) => new { a.Id, a.Name, a.Surname, a.Dob, b.SubjectId, b.Score })
  .Join(inner: subjects, outerKeySelector: a => a.Id, innerKeySelector: b => b.Id, resultSelector: (a, b) => new { a.Id, a.Name, a.Surname, a.SubjectId, a.Score, a.Dob, subjectId = b.Id, subjectTitle = b.Name })
  .Print("students.Join(grades).Join(subjects)", "\n");
Note:
I included the parameter labels to make it a bit easier to understand what each parameter does
The resultSelector's make use of c# anonymous class type to create a class without having to create it beforehand.
Console output
Code:
students.Join(grades).Join(subjects) ---> List {
{ Id = 1, Name = Jack, Surname = Sprat, SubjectId = 3, Score = 75, Dob = 1990/12/01 12:00:00 AM, subjectId = 1, subjectTitle = Biology },
{ Id = 1, Name = Jack, Surname = Sprat, SubjectId = 4, Score = 82, Dob = 1990/12/01 12:00:00 AM, subjectId = 1, subjectTitle = Biology },
{ Id = 2, Name = Mary, Surname = Contrary, SubjectId = 3, Score = 93, Dob = 1992/02/13 12:00:00 AM, subjectId = 2, subjectTitle = Economics },
{ Id = 2, Name = Mary, Surname = Contrary, SubjectId = 1, Score = 78, Dob = 1992/02/13 12:00:00 AM, subjectId = 2, subjectTitle = Economics },
{ Id = 3, Name = Snow, Surname = White, SubjectId = 3, Score = 73, Dob = 1994/06/07 12:00:00 AM, subjectId = 3, subjectTitle = Mathematics },
{ Id = 3, Name = Snow, Surname = White, SubjectId = 2, Score = 67, Dob = 1994/06/07 12:00:00 AM, subjectId = 3, subjectTitle = Mathematics }
}
 
Last edited:

[)roi(]

Executive Member
Joined
Apr 15, 2005
Messages
5,859
#18
Ok so what code is required under the cover of Linq to make that magic happen?
The following is an extract of the Linq code for Join taken from Microsoft .Net project on github:
C#:
public static IEnumerable<TResult> Join <TOuter, TInner, TKey, TResult>(this IEnumerable<TOuter> outer, IEnumerable<TInner> inner, Func<TOuter, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector, Func<TOuter, TInner, TResult> resultSelector) {
  if (outer == null) throw Error.ArgumentNull("outer");
  if (inner == null) throw Error.ArgumentNull("inner");
  if (outerKeySelector == null) throw Error.ArgumentNull("outerKeySelector");
  if (innerKeySelector == null) throw Error.ArgumentNull("innerKeySelector");
  if (resultSelector == null) throw Error.ArgumentNull("resultSelector");
  return JoinIterator < TOuter, TInner, TKey, TResult > (outer, inner, outerKeySelector, innerKeySelector, resultSelector, null);
}

static IEnumerable<TResult> JoinIterator<TOuter, TInner, TKey, TResult>(IEnumerable<TOuter> outer, IEnumerable<TInner> inner, Func<TOuter, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector, Func<TOuter, TInner, TResult> resultSelector, IEqualityComparer<TKey> comparer) {
  Lookup<TKey, TInner> lookup = Lookup<TKey, TInner> .CreateForJoin(inner, innerKeySelector, comparer);
  foreach(TOuter item in outer) {
    Lookup<TKey, TInner> .Grouping g = lookup.GetGrouping(outerKeySelector(item), false);
    if (g != null) {
      for (int i = 0; i < g.count; i++) {
        yield
        return resultSelector(item, g.elements[i]);
      }
    }
  }
}

public class Lookup<TKey, TElement>: IEnumerable<IGrouping<TKey, TElement>> , ILookup<TKey, TElement> {
  IEqualityComparer<TKey> comparer;
  Grouping[] groupings;
  Grouping lastGrouping;
  int count;

  internal static Lookup<TKey, TElement> Create <TSource> (IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector, IEqualityComparer<TKey> comparer) {
    if (source == null) throw Error.ArgumentNull("source");
    if (keySelector == null) throw Error.ArgumentNull("keySelector");
    if (elementSelector == null) throw Error.ArgumentNull("elementSelector");
    Lookup < TKey, TElement > lookup = new Lookup < TKey, TElement > (comparer);
    foreach(TSource item in source) {
      lookup.GetGrouping(keySelector(item), true).Add(elementSelector(item));
    }
    return lookup;
  }

  internal static Lookup<TKey, TElement> CreateForJoin(IEnumerable<TElement> source, Func<TElement, TKey> keySelector, IEqualityComparer<TKey> comparer) {
    Lookup<TKey, TElement> lookup = new Lookup <TKey, TElement> (comparer);
    foreach(TElement item in source) {
      TKey key = keySelector(item);
      if (key != null) lookup.GetGrouping(key, true).Add(item);
    }
    return lookup;
  }

  Lookup(IEqualityComparer<TKey> comparer) {
    if (comparer == null) comparer = EqualityComparer < TKey > .Default;
    this.comparer = comparer;
    groupings = new Grouping[7];
  }

  public int Count {
    get { return count; }
  }

  public IEnumerable<TElement> this[TKey key] {
    get {
      Grouping grouping = GetGrouping(key, false);
      if (grouping != null) return grouping;
      return EmptyEnumerable < TElement > .Instance;
    }
  }

  public bool Contains(TKey key) {
    return GetGrouping(key, false) != null;
  }

  public IEnumerator<IGrouping<TKey, TElement>> GetEnumerator() {
    Grouping g = lastGrouping;
    if (g != null) {
      do {
        g = g.next;
        yield
        return g;
      } while (g != lastGrouping);
    }
  }

  public IEnumerable<TResult> ApplyResultSelector<TResult>(Func<TKey, IEnumerable<TElement>, TResult> resultSelector) {
    Grouping g = lastGrouping;
    if (g != null) {
      do {
        g = g.next;
        if (g.count != g.elements.Length) { Array.Resize < TElement > (ref g.elements, g.count); }
        yield
        return resultSelector(g.key, g.elements);
      } while (g != lastGrouping);
    }
  }

  IEnumerator IEnumerable.GetEnumerator() {
    return GetEnumerator();
  }

  internal int InternalGetHashCode(TKey key) {
      //Microsoft DevDivBugs 171937. work around comparer implementations that throw when passed null
    return (key == null) ? 0 : comparer.GetHashCode(key) & 0x7FFFFFFF;  
  }

  internal Grouping GetGrouping(TKey key, bool create) {
    int hashCode = InternalGetHashCode(key);
    for (Grouping g = groupings[hashCode % groupings.Length]; g != null; g = g.hashNext)
      if (g.hashCode == hashCode && comparer.Equals(g.key, key)) return g;
    if (create) {
      if (count == groupings.Length) Resize();
      int index = hashCode % groupings.Length;
      Grouping g = new Grouping();
      g.key = key;
      g.hashCode = hashCode;
      g.elements = new TElement[1];
      g.hashNext = groupings[index];
      groupings[index] = g;
      if (lastGrouping == null) {
        g.next = g;
      } else {
        g.next = lastGrouping.next;
        lastGrouping.next = g;
      }
      lastGrouping = g;
      count++;
      return g;
    }
    return null;
  }

  void Resize() {
    int newSize = checked(count * 2 + 1);
    Grouping[] newGroupings = new Grouping[newSize];
    Grouping g = lastGrouping;
    do {
      g = g.next;
      int index = g.hashCode % newSize;
      g.hashNext = newGroupings[index];
      newGroupings[index] = g;
    } while (g != lastGrouping);
    groupings = newGroupings;
  }

  internal class Grouping: IGrouping <TKey, TElement>, IList <TElement> {
    internal TKey key;
    internal int hashCode;
    internal TElement[] elements;
    internal int count;
    internal Grouping hashNext;
    internal Grouping next;

    internal void Add(TElement element) {
        if (elements.Length == count) Array.Resize(ref elements, checked(count * 2));
        elements[count] = element;
        count++;
    }

    public IEnumerator < TElement > GetEnumerator() {
        for (int i = 0; i < count; i++) yield
        return elements[i];
    }

    IEnumerator IEnumerable.GetEnumerator() {
        return GetEnumerator();
    }

    // DDB195907: implement IGrouping<>.Key implicitly
    // so that WPF binding works on this property.
    public TKey Key {
      get { return key; }
    }

    int ICollection<TElement>.Count {
      get { return count; }
    }

    bool ICollection<TElement>.IsReadOnly {
      get { return true; }
    }

    void ICollection<TElement>.Add(TElement item) {
      throw Error.NotSupported();
    }

    void ICollection<TElement>.Clear() {
      throw Error.NotSupported();
    }

    bool ICollection<TElement>.Contains(TElement item) {
      return Array.IndexOf(elements, item, 0, count) >= 0;
    }

    void ICollection<TElement>.CopyTo(TElement[] array, int arrayIndex) {
        Array.Copy(elements, 0, array, arrayIndex, count);
    }

    bool ICollection<TElement>.Remove(TElement item) {
      throw Error.NotSupported();
    }

    int IList<TElement>.IndexOf(TElement item) {
      return Array.IndexOf(elements, item, 0, count);
    }

    void IList<TElement>.Insert(int index, TElement item) {
      throw Error.NotSupported();
    }

    void IList<TElement>.RemoveAt(int index) {
      throw Error.NotSupported();
    }

    TElement IList<TElement>.this[int index] {
      get {
        if (index < 0 || index >= count) throw Error.ArgumentOutOfRange("index");
        return elements[index];
      }
      set {
        throw Error.NotSupported();
      }
    }
  }
}
 

[)roi(]

Executive Member
Joined
Apr 15, 2005
Messages
5,859
#19
Ok so let's now look at recreating this using some of our FP algebras
To achieve this we'll be adding a few new extension methods to IEnumerable.
C#:
public static IEnumerable<R> JoinAP<A, B, R>(this IEnumerable<A> outer, IEnumerable<B> inner, Func<A, B, bool> predicate, Func<A, B, R> result) {
  Func<A, B, List<R>> f = (a, b) => predicate(a, b) ? new List<R> { result(a, b) } : null;
  return outer.Map(f.Curry()).Apply(inner).Flatten();
}

public static IEnumerable<U> Map<T, U>(this IEnumerable<T> ts, Func<T, U> fn) {
  return ts.Fold(new List<U>(), (a, e) => { a.Add(fn(e)); return a; });
}

public static U Fold<T, U>(this IEnumerable<T> ts, U identity, Func<U, T, U> fn) {
  var accumulator = identity;
  foreach (T element in ts) {
    accumulator = fn(accumulator, element);
  }
  return accumulator;
}

public static IEnumerable<U> FlatMap<T, U>(this IEnumerable<T> ts, Func<T, IEnumerable<U>> fn) {
  return ts.Map(fn).Flatten();
}

public static IEnumerable<U> Apply<T, U>(this IEnumerable<T> ts, IEnumerable<Func<T, U>> fn) {
  return fn.FlatMap(g => ts.Map(x => g(x)));
}

public static IEnumerable<U> Apply<T, U>(this IEnumerable<Func<T, U>> fn, IEnumerable<T> ts) {
  return ts.Apply(fn);
}

public static Func<A, Func<B, C>> Curry<A, B, C>(this Func<A, B, C> fn) => a => b => fn(a, b);

public static IEnumerable<T> Flatten<T>(this IEnumerable<IEnumerable<T>> ts) {
  return ts.Fold(new List<T>(), (a, e) => { if (e != null) { a.AddRange(e); }; return a; });
}

Ok and finally let's see how we use our FP JoinAP extension method
C#:
students
  .JoinAP(inner: grades, predicate: (a, b) => a.Id == b.StudentId, result: (a, b) => new { a.Id, a.Name, a.Surname, a.Dob, b.SubjectId, b.Score })
  .JoinAP(inner: subjects, predicate: (a, b) => a.Id == b.Id, result: (a, b) => new { a.Id, a.Name, a.Surname, a.SubjectId, a.Score, a.Dob, subjectId = b.Id, subjectTitle = b.Name })
  .Print("students.JoinAP(grades).JoinAP(subjects)", "\n");
Note:
I've taken some liberties with the API for our FP Join and decided to streamline the predicate functions used to relate keys i.e. I've replaced Linq two unary functions, with one binary function. Which not only streamlines the API, but IMO gives the Join's predicate a greater degree of flexibility.
I've also added the parameter labels to hopefully make the code easier to understand.

Console output:
Code:
students.JoinAP(grades).JoinAP(subjects) ---> List {
{ Id = 1, Name = Jack, Surname = Sprat, SubjectId = 3, Score = 75, Dob = 1990/12/01 12:00:00 AM, subjectId = 1, subjectTitle = Biology },
{ Id = 1, Name = Jack, Surname = Sprat, SubjectId = 4, Score = 82, Dob = 1990/12/01 12:00:00 AM, subjectId = 1, subjectTitle = Biology },
{ Id = 2, Name = Mary, Surname = Contrary, SubjectId = 3, Score = 93, Dob = 1992/02/13 12:00:00 AM, subjectId = 2, subjectTitle = Economics },
{ Id = 2, Name = Mary, Surname = Contrary, SubjectId = 1, Score = 78, Dob = 1992/02/13 12:00:00 AM, subjectId = 2, subjectTitle = Economics },
{ Id = 3, Name = Snow, Surname = White, SubjectId = 3, Score = 73, Dob = 1994/06/07 12:00:00 AM, subjectId = 3, subjectTitle = Mathematics },
{ Id = 3, Name = Snow, Surname = White, SubjectId = 2, Score = 67, Dob = 1994/06/07 12:00:00 AM, subjectId = 3, subjectTitle = Mathematics }
}

Conclusion:
The Linq version of Join requires 220 lines of code versus the FP version with 34 lines of code**; and if you wondering about performance; the compiled versions of Linq's Join is on average 2 times slower than FP's Applicative Functor alternative.

Clarification
**
Majority of the FP code is the implementation of the base FP algebras & combinators and therefore not specifically required for the implementation of Join (they're in contrast to Linq's implementation used for many other purposes)

The specific additional lines of code required for FP's Join is just this (i.e. 4 LOCs versus 220 LOCs):
C#:
public static IEnumerable<R> JoinAP<A, B, R>(this IEnumerable<A> outer, IEnumerable<B> inner, Func<A, B, bool> predicate, Func<A, B, R> result) {
  Func<A, B, List<R>> f = (a, b) => predicate(a, b) ? new List<R> { result(a, b) } : null;
  return outer.Map(f.Curry()).Apply(inner).Flatten();
}
 
Last edited:

[)roi(]

Executive Member
Joined
Apr 15, 2005
Messages
5,859
#20
Download a project with all the source code
The complete VS Studio project containing the source code for all of this and more can be downloaded here:
https://github.com/endofunk/codefunk.github.io/raw/master/FPIntro.zip


Note:
This project was created using Visual Studio for Mac, so to get this to work on Windows, you will need to ensure that you replace the Mac packages for SQLite with the Windows equivalent:
Screen Shot 2019-01-21 at 22.58.48.png
  • System.Data.SqlClient should work fine re its a universal package for all platforms.
  • System.Data.SQLite.Mac should be removed and replaced with System.Data.SQLite, because the .Mac dll only works on MacOS
Send me a message if you have any problems getting this project work.
 
Last edited:
Top