Skip to content

Instantly share code, notes, and snippets.

@vmarquez
Last active February 20, 2020 20:30
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save vmarquez/4640678 to your computer and use it in GitHub Desktop.
Save vmarquez/4640678 to your computer and use it in GitHub Desktop.
This is an example of using the Reader Monad for Dependency Injection using LINQ in C#. I learned about this from Tony Morris and Rúnar Bjarnason's video here: http://www.youtube.com/watch?v=ECPGTUa1WAI To figure out the (slightly weird) SelectMany peculiarities I found http://mikehadlow.blogspot.com/2011/01/monads-in-c-4-linq-loves-monads.html
//Reader Monad and its extension class to give it SelectMany(bind/flatMap) capabilities for use in LINQ queries
public static class ReaderMonadExt
{
public static ReaderMonad<T, C> SelectMany<T, A, B, C>(this ReaderMonad<T, A> rm, Func<A, ReaderMonad<T, B>> bindf, Func<A, B, C> select)
{
return new ReaderMonad<T, C>(t =>
{
var a = rm.Run(t);
return select(a, bindf(a).Run(t));
});
}
}
public class ReaderMonad<T, R>
{
private Func<T, R> _run;
public ReaderMonad(Func<T, R> run)
{
_run = run;
}
public R Run(T t)
{
return _run(t);
}
}
class ReaderMonadUsageExample
{
public static void Main(String[] args)
{
var config = GetConfig();
//Notice how LINQ gives us nice syntax opposed to nested calls to SelectMany.
var readerMonad =
from intDb in GetIntFromDB()
from netstr in GetStrFromNetwork()
from writeSuccess in WriteStuffToDisk(intDb, netstr)
select (writeSuccess ? "We wrote " + intDb + " and " + netstr + " to disk" : "error!");
var ret = readerMonad.Run(config);
Console.WriteLine(ret);
}
public static ReaderMonad<Config, Int32> GetIntFromDB()
{
return new ReaderMonad<Config, Int32>(config =>
{
config.LogMethod("Getting an int from the DB using the credentials" + config.AuthInfo);
//some db code logging in with authinfo
return 4;
});
}
//this method shows nested composition through the magic of SelectMany
public static ReaderMonad<Config, String> GetStrFromNetwork()
{
return (from aDbInt in GetIntFromDB()
from aGuid in GetGuidWithAuth()
//another method call returning a ReaderMonad that would go out over the network and get some data
select (aDbInt + "|" + aGuid + "|"));
}
private static ReaderMonad<Config, Guid> GetGuidWithAuth()
{
return new ReaderMonad<Config,Guid>(config =>
{
config.LogMethod("Getting a GUID");
return new Guid();
});
}
public static ReaderMonad<Config, Boolean> WriteStuffToDisk(int i, string str)
{
return new ReaderMonad<Config, Boolean>(config =>
{
config.LogMethod("attempting to write this to disk =" + i + str + " with the credentials " + config.AuthInfo);
//write to disk if all goes well
return true;
});
}
public static void MyCustomLog(String str)
{
Console.WriteLine("!" + str);
}
//What we are trying to Inject into all our methods. In this example we have authorization and a logging method
//Likely you could use this for sql connections, transactions, auth credentials, a pool of resources, etc.
public static Config GetConfig()
{
return new Config()
{
AuthInfo = "vmarquez",
LogMethod = (str => MyCustomLog(str)),
};
}
}
class Config
{
public String AuthInfo { get; set; }
public Action<String> LogMethod { get; set; }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment