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.
(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:
- Branching that leads to multiple return statements
- 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:
- 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.
- 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.
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.
- 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.
- 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.
- 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] . 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.
- The yield expression is the equivalent of a map invocation that returns an instance of Success with type ResolvedNewOrder.
- 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 .
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!
 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).
 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.