Monadic Complexity Resolutions

September 18, 2014

Matt Sheehe wrote a great blog post [1] where he brought up a fantastic idea.  Instead of making code more complex than it has to be, send a message to an administrator.  This solution belongs in a real special class of solutions that are invaluable to not just software engineers but problem solvers in general.  At first it might sound like a disingenuous reply to your problem, but you seriously need to ask yourself, “why can’t we send a message to an administrator?”  If you don’t have a compelling reason, then why not do it?  You may end up saving yourself a lot of problems down the road by bypassing the complex code and just going with the simple solution.

Unfortunately, this class of solutions aren’t exactly obvious.  In fact the whole class is defined by being not obvious.  But I encourage you, if you’re in charge of producing a product or setting up a system and you encounter an area where things are unusually difficult, try looking around for the non-obvious solution that makes the entire thing much easier by ignoring the problem you thought you needed to solve but didn’t really need to.

However, in my response to Matt’s post I didn’t promise advice for how to paradigm shift your way out of problems.  I promised a look into how to deal with the complexity when it turns out there are no clever solutions or there are good reasons why you can’t take the creative route.  I already published a warmup post [2] about using monads to make parsing easy.  Now let’s take a look into how monads might be used to handle a problem more similar to the one Matt brought up.

First let’s actually write out code that will make the monad work.  I’m going to use F# because it makes the code a lot less verbose, but the same kind of code can be written in C#.

type 'a Result =
     | Success of 'a
     | Failure
 type 'a Matcher = Timesheet -> 'a Result * Timesheet
 let bind (matcher : 'a Matcher) (gen : 'a -> 'b Matcher) : 'b Matcher =
     fun (ts1 : Timesheet) ->
         match matcher ts1 with
             | (Success a, ts2) -> gen a ts2
             | (Failure, _) -> (Failure, ts1)
 let unit (a : 'a) : 'a Matcher = fun ts -> (Success a, ts)

In my previous monadic parsing blog post I was using two functions that I called bind and return.  In this example I’m still using bind, but instead of using return I’m going to be using unit.  This is just an alternate name for the return function and it works exactly the same.

You might notice that the function signatures for bind and unit don’t really provide an opportunity to get a hold of internal members of a time sheet.  It’s easy enough to write helper functions that can facilitate this so let’s show an example and assume the rest are available to us.

let getStartTime (ts:Timesheet) : DateTimeOffset Result * Timesheet = (Success ts.startTime, ts)

Also, I’ve found that it’s helpful to have a “check” function that can be used to turn a boolean into a success or failure to match.  Additionally, there’s a map function that can change some value into some other value.  The map is useful because it allows you to transform values in your monad without setting up local variables.

let check (boolean:bool) (ts:Timesheet) : bool Result * Timesheet =
     match boolean with
         | true -> Success true, ts
         | false -> Failure, ts
 let map (func : ('a -> 'b)) (matcher : 'a Matcher) : 'b Matcher =
     fun (ts:Timesheet) ->
         match matcher ts with
             | Success res, ts' -> Success (func res), ts'
             | Failure, ts' -> Failure, ts'

So now that we have all of our monadic functions let’s focus on a problem to solve with them.  Maybe we need to be able to act on time sheets that span the end of a year, have vacation time and work entries that total at least 40 hours, and uses more vacation time than is available to that employee.  We can start by constructing a matcher for each sub component of this problem.

let isMultiYear =
     bind getStartTime (fun startTime ->
     bind getEndTime (fun endTime ->
     bind (check (startTime.Year <> endTime.Year)) (fun res ->
     unit res)))
 let totalIs40 =
     bind (map sumEntries getEntries) (fun entryHours ->
     bind (map sumPtoUsage getPtoUsages) (fun usageHours ->
     bind (check (entryHours + usageHours >= 40)) (fun res ->
     unit res)))     
let tooMuchVacationUsage =
     bind (map sumPtoUsage getPtoUsages) (fun usageHours ->
     bind (map sumPtoAccrual getPtoAccruals) (fun accrualHours ->
     bind (check (usageHours > accrualHours)) (fun res ->
     unit res)))

Now that we have all of the sub components we can create a single matcher that requires each one of the sub components to match in order to register a complete match.

let completeMatcher =
     bind isMultiYear (fun _ ->
     bind totalIs40 (fun _ ->
     bind tooMuchVacationUsage (fun _ ->
     unit "Error 123"))) 
let tester matcher ts =
     match matcher ts with
         | Success _, _ -> "success"
         | Failure, _ -> "failure"

There are several benefits to using monads, but the one that I’m hoping came across here is that they make it very easy to break down a problem and create a series of sub solutions.  Each sub solution works by itself but it can also work together with other sub solutions to create a greater whole.  Complex problems are best handled by breaking them into a series of simple problems.  Additionally, the complete matcher shown here may be reused as part of an even more complex matcher.

You do need to take additional care when you write monadic code.  Specifically, there are some mathematical laws that come from category theory that you need to follow with your definitions of bind and unit.  Fortunately, these laws are very simple, but talking about them is a little out of the scope of this blog post.  There’s an introduction to these sorts of concerns for Haskell [3] that gives plenty of details into the mathematical laws that you’ll need to follow.  Also if you’re interested in a lot more details, try this site [4].

[1] – 2014/08/11/human-complexity-its-not-just-for-psych-majors-anymore/

[2] – 2014/08/13/monadic-parsing/

[3] – https://www.haskell.org/haskellwiki/Monad_laws

[4] – https://www.haskell.org/haskellwiki/Typeclassopedia