A Taste of Monadic Design

The Imperative Way

All code associated with this post is available on Github.

I recently came across Scala code trying to map external IDs to entities.  The nature of this problem is financial trading.  This particular code focused on translating a newly received order from a client into an internal (resolved) version of the order.  The code read imperatively, meaning it was written in a Java style.  Take a look to see what I mean.

Imperative style order resolution

(My apologies, I’m trying to get WordPress plugin support so I can embed gists!)

On the plus side, this function returns an Either instead of throwing exceptions, however there are two significant code smells present:

  1. Branching that leads to multiple return statements
  2. Invocation of Option.get to return an Option’s value

The code flow is disjointed and it is not written idiomatically.  Can we do better?  Definitely.  One of the interesting aspects of writing Scala (or other functional languages), is that there are opportunities to learn concepts that can fundamentally alter your programming style.

Enter Monadic Design

Until just a few months, ‘monad’ was not part of my vocabulary.  Although I’ve learned a bit about monads and the larger related subject of category theory, I’m by no means an expert.  For those interested in digging deeper into the subject, I recommend this talk by Dan Rosen and these two StackOverflow posts.

Without requiring a formal understanding of monads, applicative functors, etc, I hope to show that you can write more maintainable and expressive code.  There are two keys to understanding how we can improve the existing logic:

  1. Most of the NewOrder properties need to be conditionally translated (i.e. mapped) to another form.  For example, a reference to a Symbol is required, but only if the symbol name is valid.
  2. Some of the translated NewOrder properties are required for future computations.  For example, an account ID cannot be mapped to a TradingAccount without first having a reference to a MarketTaker.

With this in mind, check out the refactored version of the order resolution code.  Rather than focusing on the details of the solution, focus on the overall code structure.

Monadic style order resolution

A lot has changed, but how do these changes relate to the fundamental issues outlined earlier?  Let’s explore the major concepts at play to better understand what is happening.

  1. All of the “questions” asked (i.e. Does a Symbol with the provided name exist?) return Option or are otherwise converted to Option.  Option expresses that there may or may not be a returned value.
  2. Each Option is translated into a Validation in order to express what should happen if the Option is a None.  In this example, the Failure type is a String that explains why resolution failed.
  3. The result of each operation is bound to a value using the for-comprehension syntax.  The bound value has useful properties.
    • The bound value is of the contained Success type.  This means that the first generator is of type Symbol, instead of Validation[String, Symbol] [1].  The binding provided by the for-comprehension is useful because it eliminates the branching statements as well as the awkward Option.get invocations, which satisfies the first key to improvement.
    • Each bound value is in scope for all operations that follow.  This property enables a Success value to be provided to future computations within the same scope, which satisfies the second key to improvement.
    • Since the bound value represents a Success, this implies that if the result of a computation is a Failure, no additional computations will occur.
  4. The yield expression is the equivalent of a map invocation that returns an instance of Success with type ResolvedNewOrder.
  5. The entire for-comprehension produces a Validation[String, ResolvedNewOrder] that will either be a String Failure or a ResolvedNewOrder Success.

Another way of gleaning insight into how this is working, is to check out the unit test that runs the same tests for both styles of resolvers.  Take some time to review both sets of code in order to better understand how the solution was migrated from an imperative to a monadic approach [2].

Even if you do not follow 100% of the changes, my hope is that you at least see the benefit in exploring monadic behavior to solve problems more expressively than in an imperative language.

Is There Room for Improvement?

The refactored solution satisfies all of the initial goals, but I think it leaves room for further improvement.  I want to focus on the three generator expressions that are bound to an unused value (i.e. _).  This is a code smell because this example’s use case for a for-comprehension is to provide locally scoped values, but the last three generators are not providing any used values.

I believe the last three generators are concerned with validation rather than resolution.  As an exercise for the interested reader, I propose separating resolution and validation.  This is an opportunity to showcase composing Validations because when performing validation, it is desirable to see all failures, rather than just the first failure.  If you are looking for inspiration for how to attack this problem, I recommend reviewing Chris Marshall’s (amusing) tale of three nightclubs and Debasish Ghosh’s post on composable domain models.

And last, but not least, I would like to express my gratitude to my colleague, Dave Stevens.  He first introduced the use of Validation and monadic design to me and he has been a great functional programming mentor to me.  Thanks Dave!

[1] In this scenario, the for-comprehension generator is syntactic sugar for a flatMap expression.  flatMap is invoked on Validation, which accepts one argument of type A => Validation[EE, B], where A is the Success type, EE is the Failure type, and B is the resulting Success type.  Given this definition, it follows that that the bound value is of type Symbol (i.e. the Success type).

[2] It should be made clear that a Validation is not a monad.  Instead, it is an applicative functor.  As I understand it, the difference between these two concepts is that unlike a monad, an applicative functor can carry forward results of previous computations.


4 responses

  1. […] I have shown that object-oriented design is not the only way to organize code, I would like to share two object-oriented principles that I believe are worth rigorously applying: […]

  2. Nice post! Just curious… looks like you are lifting all failures to success. Under what conditions is the Failure of type String produced?

  3. michaeldiamant | Reply

    Dan, thank you for reading. I think there is some confusion about the toSuccess() invocations. Scalaz OptionW provides toSuccess (http://scalaz.github.com/scalaz/scalaz-2.9.0-1-6.0/doc.sxr/scalaz/OptionW.scala.html#42422) to transform an Option into a Validation. In the example code, the provided string represents the Failure type, which is applied when the Option is None.

    For example, when SymbolDao.findByName() returns None, the returned Validation instance will be Failure containing the UnknownSymbol error message. Let me know if this makes more sense to you.

  4. Thanks for the clarification, it does indeed makes sense.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: