The BCL already has a Maybe monad by Mark Seemann
During the last couple of weeks I've been very interested in using a Maybe monad with AutoFixture's Kernel code, but although many examples can be found on the internet, they remain samples. Rinat Abdullin and Zack Owens both posted samples, but I particularly like Mike Hadlow's series about Monads in C# because he also explains how to use LINQ with monads such as the Maybe monad.
As I really wanted a Maybe monad for AutoFixture, I first thought about simply implementing it directly in the AutoFixture source. However, I found it too arbitrary to put such a general purpose programming construct into a specific library such as AutoFixture. My next thought was to create a small open source project just for that single purpose, but then I though about the problem a bit more…
The BCL sort of already has a Maybe monad - you just need to recognize it as such.
What is a Maybe monad really? If you really distill it, it's just a type that either contains a value, or doesn't contain a value. In other words, it's a type that represents a particular range: a set with either zero or one items. That's just a special case of a more general range or collection, and we already have LINQ covering those constructs.
Here it is: the Maybe monad from the BLC (encapsulated in a nice extension method):
public static class LightweightMaybe { public static IEnumerable<T> Maybe<T>(this T value) { return new[] { value }; } }
Obviously, this method returns a Maybe with a value, but we can just as easily represent Nothing with an empty array.
With my ‘new' Maybe monad, I can now write code like this (where request is a System.Object instance):
return (from t in request.Maybe().OfType<Type>() let typeArguments = t.GetGenericArguments() where typeArguments.Length == 1 && typeof(IList<>) == t.GetGenericTypeDefinition() select context.Resolve(typeof(List<>) .MakeGenericType(typeArguments))) .DefaultIfEmpty(new NoSpecimen(request)) .SingleOrDefault();
You may think that this looks dense, but before that the code looked like this:
var type = request as Type; if (type == null) { return new NoSpecimen(request); } var typeArguments = type.GetGenericArguments(); if (typeArguments.Length != 1) { return new NoSpecimen(request); } if (typeof(IList<>) != type.GetGenericTypeDefinition()) { return new NoSpecimen(request); } return context.Resolve(typeof(List<>) .MakeGenericType(typeArguments));
Notice that in this more traditional approach involving Guard Clauses, I have to construct a new NoSpecimen object in three different places, thus violating the DRY principle. I like not having all those if/return blocks in the code.
Comments
In your first code blog, rather than returning:
return new[]{value};
It would be nicer to do this:
return Enumerable.Single(value);
:)
Using Enumerable.Single(value) will not work because it takes an IEnumerable<T> and returns a T. We want the exact opposite: take a T and return IEnumerable<T>.
@other commenter: Enumerable.Repeat(value,1) does the trick you want. I sometimes cruft up a .One helper method, but I believe there's a more accepted name for it in the excellent RealWorldFunctionalProgramming in C# and F# book I dont have to hand (The one that makes Mark's head hurt :D)
It's true that there are many ways to create an IEnumerable with a single element.
var type = request as Type;
bool requestIsAType = type != null;
bool withOneGenericArgument = requestIsAType && type.GetGenericArguments().Length == 1;
bool isAGenericList = requestIsAType && type.GetGenericTypeDefinition() == typeof(IList<>);
if (requestIsAType && isAGenericList && withOneGenericArgument)
{
return context.Resolve(typeof(List<>).MakeGenericType(type.GetGenericArguments()));
}
else
{
return new NoSpecimen(request);
}
Doesn’t this just read like a functional spec? “When the request is a Type of a generic list with one generic generic argument, than … otherwise …”.
Cheers
If you call GetGenericTypeDefinition() on a type which is not generic, an exception will be thrown. This could happen if, for instance, I were to invoke the method with request = typeof(object).
If you want to play around with this, just pull the AutoFixture source and revert to revision 391 and try it out on the ListRelay class. It has pretty comprehensive test coverage.
null
as something that should semantically avoided in code, it's not only a matter of readability but also a symptom of good design. Totally agree with Mark Seemann that kindly supplied also thisMaybe
monad implementation. Excellent work.