Monday, November 22, 2010

Exploring scalaz

I have been playing around with Scalaz for a couple of weekends now. Scalaz brings to Scala some generic functions and abstractions that are not there in the current Scala API. These are mostly *functional* higher order structures that Haskell boasts as being part of their standard distribution. Using Scalaz you can write Scala code in applicative style that often come out as being more expressive than the imperative one.

Functors, applicatives, arrows, monads and many such abstractions form part of the Scalaz repertoire. It’s no wonder that using Scalaz needs a new way of thinking on your part than is with standard Scala. You need to think more like as if you’re modeling in Haskell rather than in any object-oriented language.

Typeclasses are the cornerstone of Scalaz distribution. Instead of thinking polymorphically in inheritance hierarchies, think in terms of designing APIs for the open world using typeclasses. Scalaz implements the Haskell hierarchy of typeclasses - Functors, Pointed, Applicative, Monad and the associated operations that come with them.

How is this different from the normal way of thinking ? Let’s consider an example from the current Scala point of view.

We say that with Scala we can design monadic abstractions. flatMap is the bind which helps us glue abstractions just like you would do with >>= of Haskell. But does the Scala standard library really have a monad abstraction ? No! If it had a monad then we would have been able to abstract over it as a separate type and implement APIs like sequence in Haskell ..

sequence :: Monad m => [m a] -> m [a]


We don’t have this in the Scala standard library. Consider another example of a typeclass, Applicative, which is defined in Haskell as

class Functor f => Applicative f where
  pure :: a -> f a
  (<*>) :: f (a -> b) -> f a -> f b
  …


Here pure lifts a into the effectful environment of the functor, while (<*>) takes a function from within the functor and applies it over the values of the functor. Scalaz implements Applicative in Scala, so that you can write the following:

scala> import scalaz._
import scalaz._

scala> import Scalaz._
import Scalaz._

scala> List(10, 20, 30) <*> (List(1, 2, 3) map ((_: Int) * (_: Int)).curried)
res14: List[Int] = List(10, 20, 30, 20, 40, 60, 30, 60, 90)


Here we have a pure function that multiplies 2 Ints. We curry the function and partially apply to the members of the List(1, 2, 3). Note List is an instance of Applicative Functor. Then we get a List of partial applications. Finally <*> takes that List and applies to every member of List(10, 20, 30) as a cartesian product. Of course the Haskell variant is much less verbose ..

(*) <$> [1, 2, 3] <*> [10, 20, 30]


and this is due to better type inference and curry by default strategy of function application.

You can get a more succinct variant in Scalaz using the |@| combinator ..

scala> List(10, 20, 30) |@| List(1, 2, 3) apply (_ * _)
res17: List[Int] = List(10, 20, 30, 20, 40, 60, 30, 60, 90)


You can have many instances of Applicatives so long you implement the contract that the above definition mandates. Typeclasses give you the option to define abstractions for the open world. Like List, there are many other applicatives implemented in Scalaz like options, tuples, function applications etc. The beauty of this implementation is that you can abstract over them in a uniform way through the power of the Scala type system. Just like List, you can apply <*> over options as well ..

scala> some(10) <*> (some(20) map ((_: Int) * (_: Int)).curried)
res18: Option[Int] = Some(200)


And since all Applicatives can be abstracted over without looking at the exact concrete type, here’s one that mixes an option with a function application through <*> ..

scala> some(9) <*> some((_: Int) + 3)
res19: Option[Int] = Some(12)


The Haskell equivalent of this one is ..

Just (+3) <*> Just 9


Scalaz uses two features of Scala to the fullest extent - higher kinds and implicits. The entire design of Scalaz is quite unlike the usual Scala based design that you would encounter elsewhere. Sometimes you will find these implementations quite opaque and verbose. But most of the verbosity comes from the way we encode typeclasses in Scala using implicits. Consider the following definition of map, which is available as a pimp defined in the trait MA ..

sealed trait MA[M[_], A] extends PimpedType[M[A]] {
  import Scalaz._
  //..
  def map[B](f: A => B)(implicit t: Functor[M]): M[B] = //..
  //..
}


map takes a pure function (f: A => B) and can be applied on any type constructor M so long it gets an instance of a Functor[M] in its implicit context. Using the trait we pimp the specific type constructor with the map function.

Here are some examples of using applicatives and functors in Scalaz. For fun I had translated a few examples from Learn You a Haskell for Great Good. I also mention the corresponding Haskell version for each of them ..

// pure (+3) <*> Just 10 << from lyah
10.pure[Option] <*> some((_: Int) + 3) should equal(Some(13))

// pure (+) <*> Just 3 <*> Just 5 << lyah
// Note how pure lifts the function into Option applicative
// scala> p2c.pure[Option]
// res6: Option[(Int) => (Int) => Int] = Some(<function1>)

// scala> p2c
// res7: (Int) => (Int) => Int = <function1>
val p2c = ((_: Int) * (_: Int)).curried
some(5) <*> (some(3) <*> p2c.pure[Option]) should equal(Some(15))

// none if any one is none
some(9) <*> none should equal(none)

// (++) <$> Just "johntra" <*> Just "volta" << lyah
some("volta") <*> (some("johntra") map (((_: String) ++ (_: String)).curried)) 
  should equal(Some("johntravolta"))

// more succinct
some("johntra") |@| (some("volta") apply (_ ++ _) should equal(Some("johntravolta"))


Scalaz is mind bending. It makes you think differently. In this post I have only scratched the surface and talked about a couple of typeclasses. But the only way to learn Scalaz is to run through the codebase. It's dense but if you like functional programming you will get lots of aha! moments going through it.

In the next post I will discuss how I translated part of a domain model of a financial trade system which I wrote in Haskell (Part 1, Part 2 and Part 3) into Scalaz. It has been a fun exercise for me and shows how you can write your Scala code in an applicative style.

Monday, November 01, 2010

Domain Modeling in Haskell - Applicative Functors for Expressive Business Rules

In my last post on domain modeling in Haskell, we had seen how to create a factory for creation of trades that creates Trade from an association list. We used monadic lifts (liftM) and monadic apply (ap) to chain our builder that builds up the Trade data. Here's what we did ..

makeTrade :: [(String, Maybe String)] -> Maybe Trade
makeTrade alist =
    Trade `liftM` lookup1 "account"                      alist
             `ap` lookup1 "instrument"                   alist
             `ap` (read `liftM` (lookup1 "market"        alist))
             `ap` lookup1 "ref_no"                       alist
             `ap` (read `liftM` (lookup1 "unit_price"    alist))
             `ap` (read `liftM` (lookup1 "quantity"      alist))

lookup1 key alist = case lookup key alist of
                      Just (Just s@(_:_)) -> Just s
                      _ -> Nothing


Immediately after the post, I got some useful feedback on Twitter suggesting the use of applicatives instead of monads. A Haskell newbie, that I am, this needed some serious explorations into the wonderful world of functors and applicatives. In this post let's explore some of the goodness that applicatives offer, how using applicative style of programming encourages a more functional feel and why you should always use applicatives unless you need the special power that monads offer.

Functors and Applicatives

In Haskell a functor is a typeclass defined as

class Functor f where
  fmap :: (a -> b) -> f a -> f b


fmap lifts a pure function into a computational context. For more details on functors, applicatives and all of typeclasses, refer to the Typeclassopedia that Brent Yorgey has written. In the following text, I will be using examples from our domain model of securities trading. After all, I am trying to explore how Haskell can be used to build expressive domain models with a very popular domain at hand.

Consider the association list rates from our domain model in my last post, which stores pairs of tax/fee and the applicable rates as percentage on the principal.

*Main> rates
[(TradeTax,0.2),(Commission,0.15),(VAT,0.1)]


We would like to increase all rates by 10%. fmap is our friend here ..

*Main> fmap (\(tax, amount) -> (tax, amount * 1.1)) rates
[(TradeTax,0.22000000000000003),(Commission,0.165),(VAT,0.11000000000000001)]


This works since List is an instance of the Functor typeclass. The anonymous function gets lifted into the computational context of the List data type. But the code looks too verbose, since we have to destructure the tuple within the anonymous function and thread the increment logic manually within it. We can increase the level of abstraction by making the tuple itself a functor. In fact it's so in Haskell and the function gets applied to the second component of the tuple. Here's the more idiomatic version ..

*Main> ((1.1*) <$>) <$> rates
[(TradeTax,0.22000000000000003),(Commission,0.165),(VAT,0.11000000000000001)]


Note how we lift the function across 2 levels of functors - a list and within that, a tuple. fmap does the magic! The domain model becomes expressive through the power of Haskell's higher level of abstractions.

Applicatives add more power to functors. While functors lift pure functions, with applicatives you can lift functions from one context into another. Here's how you define the Applicative typeclass.

class Functor f => Applicative f where
  pure :: a -> f a
  (<*>) :: f (a -> b) -> f a -> f b


Note pure is the equivalent of a return in monads, while <*> equals ap.

Control.Applicative also defines a helper function <$>, which is basically an infix version of fmap. The idea is to help write functions in applicative style.

(<$>) :: Applicative f => (a -> b) -> f a -> f b


Follow the types and see that f <$> u is the same as pure f <*> u.

Note also the following equivalence of fmap and <$> ..

*Main> fmap (+3) [1,2,3,4]
[4,5,6,7]

*Main> (+3) <$> [1,2,3,4]
[4,5,6,7]


Using the applicative style our makeTrade function becomes the following ..

makeTrade :: [(String, Maybe String)] -> Maybe Trade
makeTrade alist =
    Trade <$> lookup1 "account"                   alist
             <*> lookup1 "instrument"             alist
             <*> (read <$> (lookup1 "market"      alist))
             <*> lookup1 "ref_no"                 alist
             <*> (read <$> (lookup1 "unit_price"  alist))
             <*> (read <$> (lookup1 "quantity"    alist))


Can you figure out how the above invocation works ? As I said before, follow the types ..

Trade is a pure function and <$> lifts Trade onto the first invocation of lookup1, which is a Maybe functor. The result is another Maybe, which BTW is an Applicative functor as well. Then the rest of the chain continues through partial application and an applicative lifting from one context to another.

Why choose Applicatives over Monads ?

One reason is that there are more applicatives than monads. Monads are more powerful - using (>>=) :: (Monad m) => m a -> (a -> m b) -> m b you can influence the structure of your overall computation, while with applicatives your structure remains fixed. You only get sequencing of effects with an applicative functor. As an exercise try exploring the differences in the behavior of makeTrade function implemented using monadic lifts and applicatives when lookup1 has some side-effecting operations. Conor McBride and Ross Paterson has a great explanation in their functional pearl paper Applicative Programming with Effects. Applicatives being more in number, you have more options of abstracting your domain model.

In our example domain model, suppose we have the list of tax/fees and the list of rates for each of them. And we would like to build our rates data structure. ZipList applicative comes in handy here .. ZipList is an applicative defined as follows ..

instance Applicative ZipList where  
  pure x = ZipList (repeat x)  
  ZipList fs <*> ZipList xs = ZipList (zipWith (\f x -> f x) fs xs)  

*Main> getZipList $ (,) <$> ZipList [TradeTax, Commission, VAT] <*> ZipList [0.2, 0.15, 0.1]
[(TradeTax,0.2),(Commission,0.15),(VAT,0.1)]


ZipList is an applicative and NOT a monad.

Another important reason to choose applicatives over monads (but only when possible) is that applicatives compose, monads don't (except for certain pairs). The McBride and Paterson paper has lots of discussions on this.

Finally programs written with applicatives often have a more functional feel than some of the monads with the do notation (that has an intrinsically imperative feel). Have a look at the following snippet which does the classical do-style first and then follows it up with the applicative style using applicative functors.

-- classical imperative IO
notice = do
  trade <- getTradeStr
  forClient <- getClientStr
  putStrLn $ "Trade " ++ trade ++ forClient

-- using applicative style
notice = do
  details <- (++) <$> getTradeStr <*> getClientStr
  putStrLn $ "Trade " ++ details


McBride and Paterson has the final say on how to choose monads or applicatives in your design .. "The moral is this: if you’ve got an Applicative functor, that’s good; if you’ve also got a Monad, that’s even better! And the dual of the moral is this: if you want a Monad, that’s good; if you only want an Applicative functor, that’s even better!"

Applicative Functors for Expressive Business Rules

As an example from our domain model, we can write the following applicative snippet for calculating the net amount of a trade created using makeTrade ..

*Main> let trd = makeTrade [("account", Just "a-123"), ("instrument", Just "IBM"), 
                   ("market", Just "Singapore"), ("ref_no", Just "r-123"), 
                   ("unit_price", Just "12.50"), ("quantity", Just "200" )]
*Main> netAmount <$> enrichWith . taxFees . forTrade <$> trd
Just 3625.0


Note how the chain of functions get lifted into trd (Maybe Trade) that's created by makeTrade. This is possible since Maybe is an applicative functor. The beauty of applicative functors is that you can abstract this lifting into any of them. Let's lift the chain of invocation into a list of trades generating a list of net amount values for each of them. Remember List is also another applicative functor in Haskell. For a great introduction to applicatives and functors, go read Learn Yourself a Haskell for Great Good.

*Main> (netAmount <$> enrichWith . taxFees . forTrade <$>) <$> [trd2, trd3]
[Just 3625.0,Just 3375.0]


Look how we have the minimum of syntax with this applicative style. This makes business rules very expressive and not entangled into a maze of accidental complexity.

Monday, October 18, 2010

Domain Modeling in Haskell - Combinators for Composition

In my last post I had started sharing my explorations of the Haskell land in a simple domain modeling exercise. I find this a very fruitful way of learning a new language. Try something meaningful and don't feel shy to share with the community. I got some good feedback from Steven and immediately jumped on to improve the quality of the code.

In this post I will enhance and improve upon the earlier model. The focus will once again be on understanding the compositional capabilities that Haskell offers. Eric Evans in his book on domain driven design talks about supple design of the domain model. He mentions the qualities that make a design supple when "the client developer can flexibly use a minimal set of loosely coupled concepts to express a range of scenarios in the domain". I think the meat of this statement is composability. When we have a minimal set of well designed abstractions, we can make them compose in various ways to implement the functionalities that our domain needs to implement.

Combinators for business rules

Combinators are a great way to compose abstractions. If your language supports higher order functions you can compose pure functions to build up larger abstractions out of smaller ones. Concatenative languages offer the best of combinator based designs. With an applicative language Haskell is as good as it gets. I will try to enrich our earlier domain model with combinators that make domain logic explicit abstracting most of the accidental complexities off into the implementation layers.

Consider the function forTrade in my last post. It returns the list of tax/fee ids that need to be charged for the current trade. We had a stub implementation last time where it was returning the same set of tax/fee ids for every trade. Let's improve upon this and make the function return a different set of tax/fees depending upon the market of operation. Say we have a Market data type as ..

data Market = HongKong | Singapore | NewYork | Tokyo | Any deriving (Show, Eq)


where Any is a placeholder for a generic market. We will use it as a wild card in business rules to hold all rules applicable for any market. As for example taxFeeForMarket is an association list that keeps track of the tax/fee ids for every market. The one with Any in the market field specifies the generic tax/fees applicable for every market. For Singapore market we have a specialization of the rule and hence we have a separate entry for it. Let's see how we can implement a forTrade that gives us the appropriate set of tax/fee ids depending on the market where the trade is executed.

taxFeeForMarket = [(Any, [TradeTax, Commission]), (Singapore, [TradeTax, Commission, VAT])]

-- tax and fees applicable for a trade
forTrade :: Trade -> (Trade, Maybe [TaxFeeId])
forTrade trade =
  let list = lookup (market trade) taxFeeForMarket `mplus` lookup Any taxFeeForMarket
  in (trade, list)

We use a combinator mplus which is provided by the typeclass MonadPlus in Control.Monad ..

class Monad m => MonadPlus m where
  mzero :: m a
  mplus :: m a -> m a -> ma


mzero gives a zero definition for MonadPlus while mplus gives an additive semantics of the abstraction. It's also true that mzero and mplus of MonadPlus form a Monoid and there are some significant overlaps between the two definitions. But that's another story for another day. In our modeling exercise, we use the instance of MonadPlus for Maybe, which is defined as:

instance MonadPlus Maybe where
  mzero = Nothing
  
  Nothing `mplus` ys = ys
  xs `mplus` _  = xs


As you will see shortly how using mplus for Maybe nicely abstracts the business rule for tax/fee determination that we are modeling.

In the context of our tax/fee id determination logic, the combinator mplus fetches the set of tax/fees by a lookup from the specific to the generic pairs. It first looks up the entry, if any, for the specific market. If it finds one, it stops the search then and there and returns the list. Or else it goes into the second lookup and fetches the generic list. All the detailed logic of this path in which the lookup is made is encapsulated within the combinator mplus. Look how succinct the expression is, yet it reveals all the intentions that the business rule demands.

Implementing the Bounded Context

When we design a system we need to have the context map clearly defined. The primary model is the one which we are implementing. This model will collaborate with many other models or external systems. As a well-behaved modeler we need to have the interfaces well defined with all inter-module communications published beforehand.

The domain model will interact with external world from where it's going to get many of the data that it will process. Remember we defined the Trade data type in the last post. It was defined as a Haskell data type and was used all over our implementation. It is an implementation artifact which we need to localize within our impelmentation context only.

Trade data is going to come from external systems, may be over the Web as a list of key/value pairs. Hence we need to have an adaptor data structure, generic enough to supply our domain model the various fields of a security trade.

We use an association list - a list of key/value pairs for this. But how do we prepare a Trade data type from this list, without making the code base filled with boilerplatey stuff ?

Remember that a valid trade has to contain all of these fields. The moment we fail to get any of them, we need to mark the trade construction invalid and return a null trade. Null ? We know that the invention of nulls have been declared a billion dollar mistake by none other than the inventor himself.

Here we will use a monad in Haskell - the Maybe. Our constructor function will take an association list and return a Maybe Trade. Every lookup in the association list will return a Maybe String. We need to lift the String from it into the Trade data constructor. This is a monadic lift and we use two combinators liftM and ap for doing this. The moment one lookup fails, the function makeTrade returns a Nothing without proceeding with further lookups. All these details are encapsulated within these combinators, which make the following code expressive and without much of an accidental complexity.

makeTrade :: [(String, Maybe String)] -> Maybe Trade
makeTrade alist =
    Trade `liftM` lookup1 "account"                      alist
             `ap` lookup1 "instrument"                   alist
             `ap` (read `liftM` (lookup1 "market"        alist))
             `ap` lookup1 "ref_no"                       alist
             `ap` (read `liftM` (lookup1 "unit_price"    alist))
             `ap` (read `liftM` (lookup1 "quantity"      alist))

lookup1 key alist = case lookup key alist of
                      Just (Just s@(_:_)) -> Just s
                      _ -> Nothing


Now we have defined an external interface of how trades will be constructed with data coming from other modules. The domain model is thus protected through this insulation layer preventing our internal implementation from leaking out. As I mentioned earlier, the outer levels liftM and ap combinators lift data from the Maybe String that lookup1 returns into the Maybe Trade which the function makeTrade returns. Also note how we convert the String values for unit_price and quantity into the respective Double data types through the magic of typeclasses using read. Nowhere we mention that the values need to be converted to Double - it's done through type inference and the magic of automatically using the appropriate instance of the Read typeclass by the compiler.

Here are the other functions, some of them refactored from the version that we developed in the earlier post.

-- trade enrichment
enrichWith :: (Trade, [(TaxFeeId, Double)]) -> RichTrade
enrichWith (trade, taxfees) = 
    RichTrade trade $ M.fromList taxfees

-- tax fee valuation for the trade
taxFees :: (Trade, Maybe [TaxFeeId]) -> (Trade, [(TaxFeeId, Double)])
taxFees (trade, Just taxfeeids) =
    (trade, zip taxfeeids (map (valueAs trade) taxfeeids))
taxFees (trade, Nothing) =
    (trade, [])

-- calculation of each tax and fee
rates = [(TradeTax, 0.2), (Commission, 0.15), (VAT, 0.1)]
valueAs :: Trade -> TaxFeeId -> Double
valueAs trade taxFeeId = 
    (principal trade) * (fromMaybe 0 (lookup taxFeeId rates))

-- compute net amount
netAmount :: RichTrade -> NetAmount
netAmount rtrade = 
    let t = trade rtrade
        p = principal t
        m = taxFeeMap rtrade
    in M.fold (+) p m

f = enrichWith . taxFees . forTrade


Our domain model is fleshing out gradually. We have even added some stuff to pull data into our model from external contexts. In future posts I will explore more how Haskell combinators can make your domain model expressive yet succinct. This exercise is turning out to be a great learning exercise for me. Feel free to suggest improvements that will make the model better and more idiomatic.

Monday, October 04, 2010

Domain Modeling in Haskell - Follow the Types

In my last post I had talked about how implementing the same problem in different programming languages make you think differently. Of course these languages also have to be sufficiently opinionated to make you think in terms of their idioms and best practices. In that post I had discussed Scala and Clojure, two of the most competing languages trying to get the programmer mindshare on the JVM today.

As a weekend exercise I tried the same modeling problem in another functional language, that's touted to be the purest around. In my earlier exercise, Scala was statically typed, offered both functional and OO paradigms to model your domain. So it was somewhat a matter of choice to remain functional or fall down to objects for implementing your model. Clojure is functional, but still has matured in 1.2 to offer some features that you can use to build your model around OOish abstractions. Whether that's idiomatic Clojure or not, I will leave to the purists, but my friend Sergio has implemented a domain model using dynamic mixins in Clojure. A nice idea with deftypes and defprotocols.

With Haskell you don't have any escape route - you've to be functional and model your domain artifacts as functions that compose. With Haskell way of thinking you think in types, keep your pure functional code separate from impure side-effecting logic and use the myriads of ways to compose your computations. Without further ado ..

Here's the Trade abstraction as a Haskell data type .. once again much simpler compared to a real world one ..

-- some helpful synonyms
type Instrument = String
type Account = String
type NetAmount = Double

-- various types of tax and fees
data TaxFeeId = TradeTax | Commission | VAT deriving (Show, Eq, Ord)

data Trade = Trade {
      account     :: Account
     ,instrument  :: Instrument
     ,ref_no      :: String
     ,unit_price  :: Double
     ,quantity    :: Double
} deriving (Show)


We can define a helper function that gets me the principal of a trade ..

principal :: Trade -> Double
principal trade = (unit_price trade) * (quantity trade)


As in our previous exercise, we will try to decorate a trade with an appropriate set of tax and fee components. Remember we used mixins for Scala and function composition in Clojure that composes the decorators along with the subject.

The name Decorator nicely abstracts the intent of the computation. In some languages the implementation is a bit ceremonious, in others it's just a natural idiom of the language. With Haskell implementation we will also rely on composition, but we will try to get a nice pointfree computation that also makes a good DSL.

Let's find the set of tax and fees applicable for a trade. It depends on a few of the attributes of the trade and is usually computed through a somewhat detailed business logic. For our purposes, we will keep it simple and return a fixed set of tax/fee ids for our trade.

-- tax and fees applicable for a trade
forTrade :: Trade -> (Trade, [TaxFeeId])
forTrade trade =
    (trade, [TradeTax, Commission, VAT])


forTrade gives us a list of tax/fee ids applicable for the trade. However, we need to compute the value of each of them before we could use them to enrich our trade. We have the combinator taxFees that does exactly this and returns an association list containing pairs of tax/fee ids and their computed values for the trade.

taxFees use a function valueAs that implements Haskell's pattern matching. Note how the implementation is self explanatory through using algebraic data types of Haskell.

Note taxFees consume input the same way as forTrade outputs .. we are preparing to set up the composition ..

taxFees :: (Trade, [TaxFeeId]) -> (Trade, [(TaxFeeId, Double)])
taxFees (trade, taxfeeids) =
    (trade, zip taxfeeids (map (valueAs trade) taxfeeids))

-- valuation of tax and fee
valueAs :: Trade -> TaxFeeId -> Double
valueAs trade TradeTax = 0.2 * (principal trade)
valueAs trade Commission = 0.15 * (principal trade)
valueAs trade VAT = 0.1 * (principal trade)


Now we are ready for our final function that will enrich the trade. But before that let's define another type for our rich trade, that will contain a trade along with a Map of tax and fees.

import qualified Data.Map as M

type TaxFeeMap = M.Map TaxFeeId Double

data RichTrade = RichTrade {
      trade        :: Trade 
     ,taxFeeMap    :: TaxFeeMap 
} deriving (Show)


Our enrichWith function simply creates a Map out of the association list and tags it along with the Trade to form a RichTrade. Note how we use combinators like foldl to abstract the iteration and populate the Map.

-- trade enrichment
enrichWith :: (Trade, [(TaxFeeId, Double)]) -> RichTrade
enrichWith (trade, taxfees) = 
    RichTrade trade (foldl(\map (k, v) -> M.insert k v map) M.empty taxfees)


When we talk about enriching a trade in the problem domain, we talk about 3 steps - get the list of tax fees, compute the values for each of them for the particular trade and form the rich trade. Our Haskell implementation is just as verbose for the user. The enrichment recipe using the above functions boils down to the following pointfree composition ..

enrichWith . taxFees . forTrade

bah! Now how do we compute the net amount of a trade using the above model. Easy. Just define another combinator that can plug in with the above expression ..

netAmount :: RichTrade -> NetAmount
netAmount rtrade = 
    let t = trade rtrade
        p = principal t
        m = taxFeeMap rtrade
    in M.fold (\v a -> a + v) p m


and now you can compute the net amount as ..

netAmount . enrichWith . taxFees . forTrade


And we get an EDSL for free without any additional effort and just by sticking to idiomatic Haskell. The type system guides you in every respect - Haskellers call it "Follow the types". And once you have a compiled version of your code, it's type safe. If you have designed your abstractions based on the problem domain model, you have already implemented a lot of the domain rules out there.

As far as Haskell type system is concerned we have just scratched the surface here in this simple example. The idea was to compare with the implementations that Scala and Clojure had in my earlier post. In an upcoming post, I will take this example and enhance it with some more domain logic that will allow us to unearth some more of Haskell power, like type classes, a few monads etc. Incidentally there's a Reader monad begging to be modeled in the above example - can you spot it ?