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 :: (-> 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 (-> 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 => (-> 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.

6 comments:

Ricardo Herrmann said...

You forget telling readers that <$> is the operator for infix fmap before <$> shows up.

Anonymous said...

I'm not familiar with Applicatives...
I was wondering how one would write a variation of your example of increasing all rates by 10%:
((1.1*) <$>) <$> rates
that would increase tax by 10%.

Sergio

Unknown said...

@Anonymous ..

((1.1*) <$> ) <$> (filter(\(x, _) -> x == TradeTax) rates)

Christopher Done said...

I like that ((->) r) is also a Functor instance, so we can
re-define (.) to be fmap and get function composition for
free. In GHCi:

λ> let f . g = fmap f g
λ> :t (.)
(.) :: (Functor f) => (a -> b) -> f a -> f b

The following works fine:

λ> ((*4) . (+4)) . [1..5]
[20,24,28,32,36]

So instead of,

λ> ((1.1*)<$>) <$> [Just 5,Just 2]
[Just 5.5,Just 2.2]

you can write

λ> ((1.1*).) . [Just 5,Just 2]
[Just 5.5,Just 2.2]

Pretty nice.

Anonymous said...

Instead of all those references to 'alist', you could have defined 'lookup x = lookup1 x alist' in a where clause inside mkTrade. That would make it cleaner IMHO.

Anonymous said...

@Christopher: I would advise against your approach. It is useful to observe that the fmap instance for ((->) r) is (.). On the other hand, you have shown the opposite: redefining (.) as fmap is not the same at all, and highly confusing.