Top Stories - Google News

Wednesday, February 23, 2011

A case study of cleaner composition of APIs with the Reader monad

Posted by Debasish Ghosh, January 3, 2011

In my earlier post on composable domain models, I wrote about the following DSL that captures the enrichment of a security trade by computing the applicable tax/fees and then the net cash value of the trade. It uses chained composition of scalaz functors .. In this post we are going to improve upon the compositionality, introduce a new computation structure and make our APIs leaner with respect to type signatures ..

scala> (((trd1 ° forTrade) ° taxFees) ° enrichWith) ° netAmount
res0: Option[scala.math.BigDecimal] = Some(3307.5000)


Here are the building blocks for the above .. the individual functions and the type definitions for each of them ..

forTrade: Trade => (Trade, Option[List[TaxFeeId]])
taxFees: (Trade, Option[List[TaxFeeId]]) => (Trade, List[(TaxFeeId, BigDecimal)])
enrichWith: (Trade, List[(TaxFeeId, BigDecimal)]) => RichTrade
netAmount: RichTrade => Option[BigDecimal]


and here’s the chaining in action with wiring made explicit ..

figure

Note how we explicitly wire the types up so as to make the entire computation composable. Composability is a worthwhile quality to have for your abstractions. However in order for your functions to compose, the types for input and output for each of them must match. In the above example, we need to have forTrade spit out a Trade object along with the list of tax/fee id, in order for it to compose with taxFees.

For an API to be usable, the secret sauce is to make it lean. Never impose any additional burden on to your API’s interface that smells of incidental complexity to the user. This is exactly what we are doing in the above composition. Note we are carrying around the Trade argument pipelining it through each of the above functions. In our use case the Trade is a read-only state and needs to be shared amongst all functions to read the information from the object.

Enter the Reader Monad

Refactor the above into the Reader monad. A Reader is meant to be used as an environment (it’s also known as the Environment monad) for all the participating components of the computation. What we need to do for this is to set up a monadic structure for our computation. Here are the modified function signatures .. I have changed some of the names for better adaptability with the domain, but you get the idea ..

val forTrade: Trade => Option[List[TaxFeeId]] =
val taxFeeCalculate: Trade => List[TaxFeeId] => List[(TaxFeeId, BigDecimal)] =
val enrichTradeWith: Trade => List[(TaxFeeId, BigDecimal)] => BigDecimal =


Every function takes the Trade but we no longer have to do an explicit chaining by emitting the Trade also as an output. This is where a monad shines. A monad gives you a shared interface to many libraries where you don’t need to implement sequencing explicitly within your DSELs.

And here’s our DSEL that runs through the sequence of enriching a trade while using the passed in trade as an environment .. (thanks @runarorama for the help with the Reader in scalaz)

val enrich = for {
taxFeeIds <- forTrade
taxFeeValues <- taxFeeCalculate
netAmount <- enrichTradeWith
}
yield((taxFeeIds ° taxFeeValues) ° netAmount)


This is a comprehension in Scala which is like the do notation of Haskell. Desugar it as an exercise and explore how flatMap does the sequencing.

Here’s what the type of enrich looks like ..

scala> enrich
res1: (net.debasishg.domain.trade.dsl.TradeModel.Trade) => Option[BigDecimal] =


enrich is monadic in nature and follows the usual structure of a monad that sequences its operations through bind to give it an imperative look and feel. If any of the above sub-computations fail, the whole computation fails. But show it to a person who knows the domain of security trading – the steps in enrich nicely models the ubiquitous language.

I have the entire DSL in my github repo. You can get the use of enrich here in the test case ..

No comments yet.


View the original article here

No comments:

Post a Comment