Language-ext: What exactly do you use Reader<T> for?

Created on 12 Feb 2019  路  13Comments  路  Source: louthy/language-ext

I've been going through some of the old issues, as there are some gems hidden away there. Along the way, I've seen a couple of answers by @louthy where he's used the Reader<T> monad, apparently to carry state/environment around. See this one and this one for examples.

However, I can't get a clear picture of exactly what this monad gives us, and how we use it.

Can anyone give me a simple explanation, preferably with code, showing a situation in which Reader<T> provides a specific benefit. This would be really useful.

Thanks in advance.

examples / documentation

Most helpful comment

The Reader Monad is commonly used to thread configuration through application code to call sites that depend on it. It offers a form of dependency injection.

Imagine you have a UserRepository used by a number of methods. In typical c# code, you either need to pass the UserRepository to each method or use an IoC container to inject the dependency wherever it's needed.

The reader monad allows you to compose methods that require a UserRepository (or any dependency) without explicitly passing this dependency around.

For example

public class User
{
    public User(int id, string name)
    {
        Id = id;
        Name = name;
    }

    public int Id { get; }
    public string Name { get; }
}

public class UserRepository
{
    public User GetById(int id) => new User(1, "Bruce Wayne");
    public User GetByName(string name) => new User(2, "Peter Parker");
}

public Reader<UserRepository, User> GetUserByName(string name) =>
    from env in ask<UserRepository>()
    select env.GetByName(name);

public Reader<UserRepository, User> GetUserById(int id) =>
    from env in ask<UserRepository>()
    select env.GetById(id);

public Reader<UserRepository, string> GetUserAandBoss() =>
    from user in GetUserByName("some_name")
    from boss in GetUserById(user.Id)
    select $"user:{user.Name}, boss:{boss.Name}";

Notice that GetUserById and GetUserByName use UserRepository by ask'ing for it but without having to have it explicitly passed to them. Also note GetUserAandBoss is able to compose methods returning readers. Notice also that Reader has two generic types Env and A. Env is always fixed (in our case as UserRepository), but the type of A can change, the A in GetUserAandBoss isstring but the A in GetUserByName and GetUserById is User.

To actually execute, you need to call run passing in the dependency that will now be threaded throughout the composed chain and then matching on the result.

GetUserAandBoss()
    .Run(userRepository)
    .Match(x => Console.WriteLine(x), () => Console.WriteLine("Fail"));
    // outputs => user:Peter Parker, boss:Bruce Wayne

So to sum up, Reader is typically used for dependency injection (as shown in https://github.com/louthy/language-ext/issues/515) or configuration. You have a Configuration object with a bunch a settings that the methods throughout your code base need to access, but you don't want to have to explicitly pass Configuration throughout your application. Instead you have a Reader<Configuration, A> and then at the entry point of you application, you load your Configuration from somewhere and call Run.

Having said also this, I'm new to all this myself and am happy to be corrected.

All 13 comments

The Reader Monad is commonly used to thread configuration through application code to call sites that depend on it. It offers a form of dependency injection.

Imagine you have a UserRepository used by a number of methods. In typical c# code, you either need to pass the UserRepository to each method or use an IoC container to inject the dependency wherever it's needed.

The reader monad allows you to compose methods that require a UserRepository (or any dependency) without explicitly passing this dependency around.

For example

public class User
{
    public User(int id, string name)
    {
        Id = id;
        Name = name;
    }

    public int Id { get; }
    public string Name { get; }
}

public class UserRepository
{
    public User GetById(int id) => new User(1, "Bruce Wayne");
    public User GetByName(string name) => new User(2, "Peter Parker");
}

public Reader<UserRepository, User> GetUserByName(string name) =>
    from env in ask<UserRepository>()
    select env.GetByName(name);

public Reader<UserRepository, User> GetUserById(int id) =>
    from env in ask<UserRepository>()
    select env.GetById(id);

public Reader<UserRepository, string> GetUserAandBoss() =>
    from user in GetUserByName("some_name")
    from boss in GetUserById(user.Id)
    select $"user:{user.Name}, boss:{boss.Name}";

Notice that GetUserById and GetUserByName use UserRepository by ask'ing for it but without having to have it explicitly passed to them. Also note GetUserAandBoss is able to compose methods returning readers. Notice also that Reader has two generic types Env and A. Env is always fixed (in our case as UserRepository), but the type of A can change, the A in GetUserAandBoss isstring but the A in GetUserByName and GetUserById is User.

To actually execute, you need to call run passing in the dependency that will now be threaded throughout the composed chain and then matching on the result.

GetUserAandBoss()
    .Run(userRepository)
    .Match(x => Console.WriteLine(x), () => Console.WriteLine("Fail"));
    // outputs => user:Peter Parker, boss:Bruce Wayne

So to sum up, Reader is typically used for dependency injection (as shown in https://github.com/louthy/language-ext/issues/515) or configuration. You have a Configuration object with a bunch a settings that the methods throughout your code base need to access, but you don't want to have to explicitly pass Configuration throughout your application. Instead you have a Reader<Configuration, A> and then at the entry point of you application, you load your Configuration from somewhere and call Run.

Having said also this, I'm new to all this myself and am happy to be corrected.

@michael-wolfenden Wow, that's brilliant! Much clearer.

Few questions if you don't mind...

1) What does the ask() function actually do? I've looked at the signature and the code, but can't work it out.

2) The Match() function takes two parameters, the first is an Action on the result, which I understand, but the second looks like it gets called if something went wrong. What would go wrong? Obviously an exception would trigger this being called, but anything else? UPDATE: It looks like Match() is returning a Try, in which case this will only be called if there is an exception. Is that correct?

3) Following on from the previous question (and so might be answered by the answer to that), the second parameter passed to Match() doesn't contain any information about what went wrong, so seems a bit useless. It looks like you can't actually get at the exception details. Again, this question may just be be me not understanding its use.

4) Finally (for now!), I tried a variation of your idea, where the repository returned an Option<T> instead of an object. See the code below. When I run this passing a valid ID, I get an Option<Ferret> as expected, when I pass a negative ID I get Fail printed out, but if I pass in a positive number that is not in the repository, such as 10, I get what looks like an empty collection. Why is that? I naively thought that if I passed back a None from the repository method, it would either show me None or call the second Action passed to Match(). I'm not actually sure what is happening here. UPDATE: With some more playing around, I realised that I was actually getting None back, it was just that LinqPad's default way of displaying a None is as if it is an empty collection (which in a way it is). So, this question can be ignored.

Thanks again for the great reply. I hope you don't mind me asking more. You might be new to this, but you've obviously got a much better understanding than I have!

Here is the code I used. If you use LinqPad, add the package and namespaces, set to C# Program and paste this lot in...

void Main() {
  GetFerretByIDRunAndMatch(-1);
  GetFerretByIDRunAndMatch(1);
  GetFerretByIDRunAndMatch(10);
}

public class Ferret {
  public Ferret(int id, string name) {
    ID = id;
    Name = name;
  }

  public int ID { get; }
  public string Name { get; }
}

public class FerretRepository {
  private List<Ferret> _ferrets = new List<Ferret> {
    new Ferret(1, "Freddy"),
    new Ferret(2, "Ferdina"),
    new Ferret(3, "Frogface"),
  };

  public Option<Ferret> GetFerretByID(int id) =>
    id < 0 ? throw new ArgumentException($"{id} is not a valid value for a ferret ID") : _ferrets.FirstOrDefault(f => f.ID == id);
  public Option<Ferret> GetFerretByName(string name) =>
    _ferrets.FirstOrDefault(f => f.Name == name);
}

public Unit GetFerretByIDRunAndMatch(int id) =>
  GetFerretByID(id)
    // Creating a new repository for simplicity
    .Run(new FerretRepository())
    .Match(x => Console.WriteLine(x),
           () => Console.WriteLine("Exception")
    );

public Reader<FerretRepository, Option<Ferret>> GetFerretByID(int id) =>
  from env in ask<FerretRepository>()
  select env.GetFerretByID(id);

public Reader<FerretRepository, Option<Ferret>> GetFerretByName(string name) =>
  from env in ask<FerretRepository>()
  select env.GetFerretByName(name);
  1. What does the ask() function actually do? I've looked at the signature and the code, but can't work it out.

I'll try to explain it as I understand it, however forgive me as my terminology might not be 100% correct.

Lets start with this example

public class Configuration
{
    public string Url { get; }
    public Configuration(string url) => Url = url;
}

public Reader<Configuration, string> UppercaseUrl() =>
    from config in ask<Configuration>()
    select config.Url.ToUpper();

Starting from the top Reader<Env, A> is defined as a delegate.

public delegate(A Value, bool IsFaulted) Reader<Env, A>(Env env);

Since Reader is a delegate, our UppercaseUrl function which returns a Reader<Configuration, string> can actually be written without using ask.

It just needs to return a function that takes a Env and returns a (A Value, bool IsFaulted).

In the case of UppercaseUrl, Env is Configuraion and A is string.

So

public Reader<Configuration, string> UppercaseUrl() =>
    from config in ask<Configuration>()
    select config.Url.ToUpper();

can actually be written as

public Reader<Configuraion, string> UppercaseUrlWithoutAsk()
    => config
    => (config.Url.ToUpper(), false);

but in my opinion, the ask syntax looks nicer.

But how is ask defined?

Well looking at

public Reader<Configuration, string> UppercaseUrl() =>
    from config in ask<Configuration>()
    select config.Url.ToUpper();

We know two things about ask.

Firstly, the thing that ask is returning must be a Reader<Configuration, ?> as the query syntax from reader only works if the Env is fixed and the type of monad is the same (Reader).

Secondly, we know the A is of type Configuration because when you write from a in Reader<Env, A>, a will be of type A.

So ask is our case must have the type signature

public Reader<Configuration, Configuration> Ask() 

or more generically

public Reader<Env, Env> Ask() 

and for its implemenation, as above, it needs to return a function that takes a Env and returns a (Env Value, bool IsFaulted)

So the definition must be

public Reader<Env, Env> Ask()
    => env
    => (env, false);

You can see the formal definition https://github.com/louthy/language-ext/blob/49bc1dd7baa80ea8ad8cde7d0359f5c906dcaeb6/LanguageExt.Core/ClassInstances/Monad/MReader.cs#L43

  1. The Match() function takes two parameters, the first is an Action on the result, which I understand, but the second looks like it gets called if something went wrong. What would go wrong? Obviously an exception would trigger this being called, but anything else?
  2. Following on from the previous question (and so might be answered by the answer to that), the second parameter passed to Match() doesn't contain any information about what went wrong, so seems a bit useless. It looks like you can't actually get at the exception details. Again, this question may just be be me not understanding its use.

I'm with you, I found the Match() to be confusing as well, perhaps @louthy can shed some light

@michael-wolfenden You posted your reply just as I was updating question 4!

Thanks for the explanation of ask. I'm going to need to read it a bit more, but at first skim, it definitely looks like it clarifies it.

As for Match, as I said in my update, it looks like it's returning a Try, meaning that the Fail lambda is only called if there is an exception. You can just use the normal Try.Match function, whose overloads include at least one version where the exception is passed in as a parameter.

What I can't work out is when the None function would be called. I modified my function to look like this...

public Unit GetFerretByIDRunAndMatchOld(int id) =>
    GetFerretByID(id)
        // Creating a new repository for simplicity
        .Run(new FerretRepository())
        .Match(
            Some: x => Console.WriteLine(x),
            None: () => Console.WriteLine("None"),
            Fail: ex => Console.WriteLine($"Exception: {ex.Message}")
        );

...but the None lambda was never called, even if the ferret wasn't found. I'm not sure how Try could ever be None as I thought it was either (no pun intended) the target value or the exception.

Hopefully @louthy will help us out.

Thanks again. I'll read your explanation a few more times and see if I get it.

@michael-wolfenden Actually, going right back to the beginning, I still have a basic question.

In your original explanation of why Reader would be used, you said "_It offers a form of dependency injection_"

Looking at it again, I'm not sure what we have gained. Consider my GetFerretByIDRunAndMatch() function, modified to take a repository as a parameter (as I presume newing one up inside the function is exactly what we don't want to do!)...

public Unit GetFerretByIDRunAndMatch(FerretRepository ferretRepository, int id) =>
    GetFerretByID(id)
        .Run(ferretRepository)
        .Match(
            Some: f => Console.WriteLine(f),
            None: () => {},
            Fail: ex => Console.WriteLine($"Exception: {ex.Message}")
        );

Compare that to the way I would have written it without Reader...

public Unit GetFerretByIDWithoutReader(FerretRepository ferretRepository, int id) =>
  ferretRepository.GetFerretByID(id)
    .Match(
      Some: f => Console.WriteLine(f),
      None: () => Console.WriteLine("None")
    );

If you ask me, the second version is a lot clearer.

So what did I miss? What did we gain by using the Reader?

Thanks again.

Sure, if your only ever calling a single method with your dependency then there is no advantage, however consider a typical application where FerretRepository is probably called from a 100 different places within your application.

You could choose to pass this dependency (state) around to each point in the application that its needed and infect every method signature, or use the Reader monad and pass the dependency once at the entry point of the application.

@michael-wolfenden

Hmm, not sure I see how it helps. You still have to pass the repository around somehow. In both of the cases I showed above, it was passed in as a parameter. How does using the Reader make any difference?

Sorry if I missed something here. Thanks again.

If you look at the original example, none of the methods GetUserAndBoss, GetUserById and GetUserByName require the repository. The only time a repository is involved in at the entry point of the application when run is called.

If you try to rewrite this as in your ferret example, each of these three methods would need the repository passed. The biggest smell being that GetUserAndBoss needs a repository passed to it that it doesn't use. It's just needs it to transparently pass to the other two methods it calls. This is the problem that the reader monad attempts to solve.

@michael-wolfenden

Ah, I think the light is beginning to dawn! I think I confused myself when I played around with your code, as I named the two functions in FerretRepository with the same names as the two functions outside. I think also having them all in the one LinqPad file made me forget that they would normally be in separate classes.

Looking at this code...

UserRepository userRepository = new UserRepository();
GetUserAandBoss()
    .Run(userRepository)
    .Match(x => Console.WriteLine(x),
                 () => Console.WriteLine("Fail")
    );

This could be called from (say) a view model, which being the top-level in the hierarchy is responsible for creating the dependencies (the user repository in this case). GetUserAandBoss() could be in a business logic class, and doesn't need to be given a repository, as it will get the one created in the view model when we call Run.

Clever isn't it? Thanks again for the help, if I am correct in my summary above, then I think I have it now.

One (possibly) last question. What if I want to pass in more than one dependency? Let's say my business logic class needs two (or more) different repositories. How do I do that?

What if I want to pass in more than one dependency? Let's say my business logic class needs two (or more) different repositories. How do I do that?

You could create a class World that had a UserRepository and a OrderRepository property, the call .Run(new World(userRepository, orderRepository))

@michael-wolfenden

Thanks for that. I have seen @louthy do that, but didn't realise what he was doing until now.

I just had a play, and came up with something that sort of simulates a view / view model / business logic / repository hierarchy, to make sure I got the overall picture correct.

Please can you have a look at the following code and see if it looks right. Any comments welcome. This is a complete sample, which you can paste into LinqPad and run...

void Main() {
  // Imagine this is a view, which creates a view model (possibly from a DI container).
  MyViewModel mvm = new MyViewModel();
  // Commands bound to controls on the view might call view model methods as follows...
  mvm.RunGetFerretByID(1);
  mvm.RunGetFerretByID(10);
  mvm.RunGetFerretByID(-1);
}

public class MyViewModel {
  // This would probably be injected, not newed up
  private FerretRepository _ferretRepository = new FerretRepository();

  public void RunGetFerretByID(int id) {
    MyBusinessLogic.GetFerretByID(id)
      .Run(_ferretRepository)
      .Match(
        Some: x => Console.WriteLine(x),
        None: () => Console.WriteLine("None"),
        Fail: ex => Console.WriteLine($"Exception: {ex.Message}")
      );
  }
}

public static class MyBusinessLogic {
  // Yes, in this case the business logic class isn't doing anything useful,
  // but in a real scenario it might do more
  public static Reader<FerretRepository, Option<Ferret>> GetFerretByID(int id) =>
    from ferretRepo in ask<FerretRepository>()
    select ferretRepo.GetFerretByID(id);
}

public class FerretRepository {
  private List<Ferret> _ferrets = new List<Ferret> {
    new Ferret(1, "Freddy"),
    new Ferret(2, "Ferdina"),
    new Ferret(3, "Frogface"),
  };

  public Option<Ferret> GetFerretByID(int id) =>
    id < 0 
      ? throw new ArgumentException($"{id} is not a valid value for a ferret ID") 
      : _ferrets.FirstOrDefault(f => f.ID == id);
}

public class Ferret {
  public Ferret(int id, string name) {
    ID = id;
    Name = name;
  }

  public int ID { get; }
  public string Name { get; }
}

Thanks again.

It's just needs it to transparently pass to the other two methods it calls. This is the problem that the reader monad attempts to solve.

I really like this comment.

I have noticed that an expression with (say) two dependencies can either be the entire body of a function that requires both dependencies or part of the curried version of that function, which effectively moves a type from being part of the input to being part of the output.

For example, C Function(A a, B b) curries to Func<B, C> CurriedFunction(A a) and the type B has "moved" from the being part o the input (A and B) to being part of the output (Func<B, C>).

Since you have to "see" B either on the right as part of the input or on the left as part of the output, I don't see an advantage one way or the other from a syntactic perspective.

What does seem different those is a separation of "what to do" and "when to do it" regarding B.

If your signature is C Function(A a, B b), then when it finishes executing, A and B have (presumably) been used and C has been computed. In particular, any side effects involved with accessing B have occurred.

On the other had, if your signature is Func<B, C> CurriedFunction(A a), then when it finishes executing, how to use B has been specified, but B hasn't been used yet, so any side effects involved with accessing B have _not_ occurred.

This seems like the real advantage to me.

The example I gave above comparing a function and its curried form is how I first became aware of this situation. However, a curried function is certainly just a special case of separating "what to do" and "when to do it". My impression is that the "purpose" of the reader monad could be to preciously express the separation "what to do" and "when to do it" in the case of reading (as opposed to the case of writing, which is handled by the writer monad).

What if I want to pass in more than one dependency? Let's say my business logic class needs two (or more) different repositories. How do I do that?

You could create a class World that had a UserRepository and a OrderRepository property, the call .Run(new World(userRepository, orderRepository))

Thanks for a great thread guys!!

How about passing the entire IoC container into the reader and resolving inside the functions? Is this 'wrong'? You could have some power about setting up scopes / units of work as well.

The below was done in LinqPad with Autofac.
http://share.linqpad.net/ux4bd4.linq

void Main()
{
    var container = GetContainer();

    using (var scoped = container.BeginLifetimeScope()) // optionally create a specific scope / unit of work.
    {
        GetUserAandBoss().Run(scoped).Match(x => x.Dump("run 1"), () => Console.WriteLine("Fail"));
        GetUserAandBoss().Run(scoped).Match(x => x.Dump("run 2"), () => Console.WriteLine("Fail"));
    }
}

public ILifetimeScope GetContainer()
{
    var builder = new ContainerBuilder();
    //builder.RegisterType<UserRepository>().AsSelf().SingleInstance(); // single instance for all resolutions.
    //builder.RegisterType<UserRepository>().AsSelf(); // instance per dependency.
    builder.RegisterType<UserRepository>().AsSelf().InstancePerLifetimeScope();
    builder.RegisterType<Weather>().AsSelf().InstancePerLifetimeScope();
    return builder.Build();
}

public class User
{
    public User(int id, string name)
    {
        Id = id;
        Name = name;
    }

    public int Id { get; }
    public string Name { get; }
}

public class Weather
{
    public string Forecast => "Sunny";
}

public class UserRepository
{
    public Guid Key { get; set; }
    public UserRepository()
    {
        Key = Guid.NewGuid(); // just used to show when we get new instances
    }

    public User GetById(int id) => new User(1, $"Bruce Wayne {Key}");
    public User GetByName(string name) => new User(2, $"Peter Parker {Key}");
}

public Reader<ILifetimeScope, User> GetUserByName(string name) =>
    from env in ask<ILifetimeScope>()
    select env.Resolve<UserRepository>().GetByName(name);

public Reader<ILifetimeScope, User> GetUserById(int id) =>
    from env in ask<ILifetimeScope>()
    select env.Resolve<UserRepository>().GetById(id);

public Reader<ILifetimeScope, string> GetWeather() =>
    from env in ask<ILifetimeScope>()
    select env.Resolve<Weather>().Forecast;

public Reader<ILifetimeScope, string> GetUserAandBoss() =>
    from user in GetUserByName("some_name")
    from boss in GetUserById(user.Id)
    from weather in GetWeather()
    select $"user:{user.Name}, boss:{boss.Name}, weather:{weather}";

Was this page helpful?
0 / 5 - 0 ratings