Temporal coupling

Next let's define a few Interfaces:
C#:
namespace ReaderMonadDI { 
  public interface IScholar {
    List<Student> Students();
  }

  public interface IAcademia {
    List<Grade> Grades();
  }

  public interface ISchool {
    IScholar Scholar();
    IAcademia Academia();
  }
}

Now let's create two rudimentary data sources to resemble production and mock scenarios (I'm purposefully skipping any network to avoid any convoluting the examples):

Production data sources:
C#:
namespace ReaderMonadDI {
  public class ProdScholar : IScholar {
    public List<Student> Students() {
      return new List<Student> {
        Student.Create("Jack", 1),
        Student.Create("John", 2),
        Student.Create("Jill", 3),
        Student.Create("James", 4),
        Student.Create("Jean", 5)
      };
    }
  }

  public class ProdAcademia : IAcademia {
    public List<Grade> Grades() {
      return new List<Grade> {
        Grade.Create(1, 2018, Subject.Math, 45),
        Grade.Create(1, 2018, Subject.Science, 65),
        Grade.Create(1, 2018, Subject.English, 68),
        Grade.Create(2, 2018, Subject.Math, 33),
        Grade.Create(2, 2018, Subject.Math, 87),
        Grade.Create(2, 2018, Subject.English, 43),
        Grade.Create(3, 2018, Subject.Science, 71),
        Grade.Create(3, 2018, Subject.Math, 76),
        Grade.Create(3, 2018, Subject.English, 85),
        Grade.Create(4, 2018, Subject.Science, 84),
        Grade.Create(4, 2018, Subject.Math, 92),
        Grade.Create(4, 2018, Subject.Math, 79),
        Grade.Create(5, 2018, Subject.English, 83),
        Grade.Create(5, 2018, Subject.Math, 77),
        Grade.Create(5, 2018, Subject.Science, 68)
      };
    }
  }
}

Mock data sources:
C#:
namespace ReaderMonadDI {
  public class MockScholar : IScholar {
    public List<Student> Students() {
      return new List<Student> { Student.Create("Test Student", 1) };
    }
  }

  public class MockAcademia : IAcademia {
    public List<Grade> Grades() {
      return new List<Grade> {
        Grade.Create(1, 2018, Subject.Math, 80),
        Grade.Create(1, 2018, Subject.Science, 76)
      };
    }
  }
}
 
The Environment
The reader monad at its crux is a function of the form f(E) -> A; where the E represents the environment we are passing in as a parameter (injecting), and the A represents the value that we are extracting; naturally it's a function, so the A is the result of the computation of the function f(E) -> A, where the E; environment has been passed in.

Ok the reader monad requires an environment; lets's define one that will encapsulate our dependencies:
C#:
namespace ReaderMonadDI {
  public class SchoolEnvironment : ISchool {
    private readonly IScholar scholar;
    private readonly IAcademia academia;
    
    public SchoolEnvironment(IScholar scholar, IAcademia academia) {
      this.scholar = scholar;
      this.academia = academia;
    }
    
    private SchoolEnvironment() {}

    public IScholar Scholar() {
      return scholar;
    }

    public IAcademia Academia() {
      return academia;
    }
  }
}
 
Ok let's get onto the creating functions that are dependant on this environment (& dependencies):
C#:
namespace ReaderMonadDI {
  public static class School {
    private static class Find {
      public static Func<ISchool, int> IDUsingName(string studentName) {
        return env => { return env.Scholar().Students().Filter(s => s.lastname.Contains(studentName)).First().id; };
      }

      public static Func<ISchool, string> NameUsingID(int studentID) {
        return env => { return env.Scholar().Students().Filter(s => s.id == studentID).First().lastname; };
      }

      public static Func<ISchool, List<Grade>> GradesUsingID(int studentID) {
        return env => { return env.Academia().Grades().Filter(grade => grade.id == studentID).ToList(); };
      }
    }

    public static Reader<ISchool, int> IDForName(string studentName) {
      return Reader(Find.IDUsingName(studentName));
    }

    public static Reader<ISchool, string> NameForId(int studentID) {
      return Reader(Find.NameUsingID(studentID));
    }

    public static Reader<ISchool, List<Grade>> GradesForID(int studentID) {
      return Reader(Find.GradesUsingID(studentID));
    }

    public static Reader<ISchool, List<Grade>> GradesForName(string studentName) {
      return IDForName(studentName).Bind(GradesForID);
    }
  }
}

Ok so let's summarise, we have created 4 functions:
  • public static Reader<ISchool, int> IDForName(string studentName)
  • public static Reader<ISchool, string> NameForId(int studentID)
  • public static Reader<ISchool, List<Grade>> GradesForID(int studentID)
  • public static Reader<ISchool, List<Grade>> GradesForName(string studentName)
Basically a function to retrieve a student's ID given a studentName; secondly a student's Name given a studentID, and thirdly a student's Grades for a given studentID, and finally a students's Grades for a given studentName.

Note:
The last function requires that the student Name be first linked to an ID before the grades can be acquired: cross parameter dependency. Note the use of Monadic bind to compose these two sub computations.
Also the dependency is defined in the return type signature for each of these functions; essentially we are returning a functions that requires the environment dependency before it will compute its result. The benefit of this is that we have now decoupled the function from a class, so that injection can be done at the point in our code where we need it. Secondly we have essentially partially applied this part of the computation so we can lazily bind it with other actions, or use it as input to another process, or even add it to an array of computations for later on mass execution.
 
Last edited:
Finally let's see a few usage examples of this; first tied to our production data sources:
C#:
namespace ReaderMonadDI {
  class MainClass {
    public static void Main(string[] args) {
      var prod_scholar = new ProdScholar();
      var prod_academia = new ProdAcademia();
      var prod_school = new SchoolEnvironment(prod_scholar, prod_academia);

      Console.WriteLine("\nProd --------------------------------------");
      School.IDForName("Jean")
            .Run(prod_school)
            .IfSome(Console.WriteLine);
     
      School.NameForId(2)
            .Run(prod_school)
            .IfSome(Console.WriteLine);

      School.GradesForName("Jean")
            .Run(prod_school)
            .IfSome(g => g.ForEach(Console.WriteLine));
     
      School.GradesForID(3)
            .Run(prod_school)
            .IfSome(g => g.ForEach(Console.WriteLine));
     
      var grades = School.GradesForID(1)
                         .Run(prod_school)
                         .IfNoneOrFail(new List<Grade>());
      grades.ForEach(Console.WriteLine);
     
      var gradeAverage = School.IDForName("Jack")
                               .Bind(School.GradesForID)
                               .Map(lg => lg.Filter(g => g.year == 2018).ToList())
                               .Map(lg => lg.Map(g => g.score).ToList())
                               .Run(prod_school)
                               .IfNoneOrFail(new List<int>())
                               .Average();
      Console.WriteLine("gradeAverage : {0}", (int)gradeAverage);
    }
  }
}

The output from this is:
Code:
Prod --------------------------------------
5
John
Grade: [id: 5, year: 2018, subject: English, score: 83]
Grade: [id: 5, year: 2018, subject: Math, score: 77]
Grade: [id: 5, year: 2018, subject: Science, score: 68]
Grade: [id: 3, year: 2018, subject: Science, score: 71]
Grade: [id: 3, year: 2018, subject: Math, score: 76]
Grade: [id: 3, year: 2018, subject: English, score: 85]
Grade: [id: 1, year: 2018, subject: Math, score: 45]
Grade: [id: 1, year: 2018, subject: Science, score: 65]
Grade: [id: 1, year: 2018, subject: English, score: 68]
gradeAverage : 59
 
Last edited:
And lastly an example of test mocking:
C#:
namespace ReaderMonadDI {
  class MainClass {
    public static void Main(string[] args) {
      Console.WriteLine("\nMock --------------------------------------");

      var mock_scholar = new MockScholar();
      var mock_academia = new MockAcademia();
      var mock_school = new SchoolEnvironment(mock_scholar, mock_academia);

      var getidforname_ok_test = School.IDForName("Test Student")
                                       .Run(mock_school)
                                       .IfNoneOrFail(-1);
      Console.WriteLine("GetIDForName(\"Test Student\") == 1 : {0}", getidforname_ok_test == 1);

      var getnameforid_ok_test = School.NameForId(1)
                                       .Run(mock_school)
                                       .IfNoneOrFail("Not Found!");
      Console.WriteLine("GetNameForId(1) == \"Test Student\" : {0}", getnameforid_ok_test == "Test Student");
     
      var getnameforid_fail_test = School.NameForId(2)
                                         .Run(mock_school)
                                         .IfNoneOrFail("Not Found!");
      Console.WriteLine("GetNameForId(2) == \"Not Found!\" : {0}", getnameforid_fail_test == "Not Found!");

      var gradeAverage_test = School.GradesForName("Test Student")
                                    .Map(lg => lg.Map(g => g.score).ToList())
                                    .Run(mock_school)
                                    .IfNoneOrFail(new List<int>())
                                    .Average();
     
      Console.WriteLine("gradeAverage_test for \"Test Student\" == 78 : {0}", (int)gradeAverage_test == 78);
    }
  }
}

And the output from this is:
Code:
Mock --------------------------------------
GetIDForName("Test Student") == 1 : True
GetNameForId(1) == "Test Student" : True
GetNameForId(2) == "Not Found!" : True
gradeAverage_test for "Test Student" == 78 : True

Hopefully that clears things up a bit about the reader monad; a simple abstraction that encapsulates a unary function, including conformances to functor, monad and applicative.

Note:
In the code I have used a C# functional library called LanguageExt.Core to provide the reader monad. It is however a very simple abstraction so this code dependency can be avoided quite easily.
 
Last edited:
Top
Sign up to the MyBroadband newsletter
X