public
Last active

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

  • Download Gist
ReaderMonad.cs
C#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
//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; }
}

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.