Securities trading is a domain on which I have been working since the last 10 years. I have implemented domain models of securities trading back office systems in Java and Scala. It's time to add scalaz to the mix and see how much more functional my model turns out to be. I have created a playground for this - tryscalaz is a repository on my github that hosts some of my experiments with scalaz. I have started building a domain model for trading systems. It's far from being a realistic one for production use - its main purpose is to make myself more conversant with scalaz.
Scalaz is a wonderful experiment - it's definitely what functional Scala programs should look like. It has a small but wonderful community - Jason (@retronym) and Runar (@runarorama) always help me proactively both on the mailing list and on Twitter.
I am not going into every detail of how my trade domain model shapes up with Scalaz. I implemented a similar domain model in Haskell very recently and documented it here, here and here on my blog. If nothing else, it will help you compare the various aspects of both the implementations.
In this post let me go through some of the features of Scalaz that I found wonderfully expressive to model your domain constraints. You can get a lot out of using Scala only. But with Scalaz, you can take your composition at a much higher level through the various combinators that it offers as part of implementing typeclasses for functors, applicatives, monads and arrows. I haven't yet explored all of these abstractions - yet many of those are already very useful in making your domain models concise, yet expressive.
Here's some example of composition using the higher order functions that Scalaz offers ..
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
scala> import scalaz._ | |
import scalaz._ | |
scala> import Scalaz._ | |
import Scalaz._ | |
scala> import net.debasishg.domain.trade.Trades._ | |
import net.debasishg.domain.trade.Trades._ | |
// a Map for trade attributes | |
scala> val t1 = Map("account" -> "a-123", "instrument" -> "google", "refNo" -> "r-123", "market" -> "HongKong", "unitPrice" -> "12.25", "quantity" -> "200") | |
t1: scala.collection.immutable.Map[java.lang.String,java.lang.String] = Map((quantity,200), (market,HongKong), (refNo,r-123), (account,a-123), (unitPrice,12.25), (instrument,google)) | |
// get a Trade out of it | |
scala> val trd1 = makeTrade(t1) | |
trd1: Option[net.debasishg.domain.trade.Trades.Trade] = Some(Trade(a-123,google,r-123,HongKong,12.25,200)) | |
// map .. Scala style | |
scala> (((trd1 map forTrade) map taxFees) map enrichWith) map netAmount | |
res0: Option[scala.math.BigDecimal] = Some(3307.5000) |
Note how we can compose the functions much like the Haskell way that I described in the earlier posts. In the above composition, I used map, which we can do in Scala for lists or options which explicitly support a map operation that maps a function over the collection. With scalaz we can use mapping of a function over any
A
of kind *->*
for which there exists a Functor[A]
. Scala supports higher kinds and scalaz uses it to make map
available more generally than what you get in the Scala standard library.Now let's infuse some more Scalaz magic into it. Frequently we need to do the same operations on a list of trades, which means that instead of just a map, we need to lift the functions through another level of indirection. Much like ..
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// another trade | |
scala> val t2 = Map("account" -> "b-123", "instrument" -> "ibm", "refNo" -> "r-234", "market" -> "Singapore", "unitPrice" -> "15.25", "quantity" -> "400") | |
t2: scala.collection.immutable.Map[java.lang.String,java.lang.String] = Map((quantity,400), (market,Singapore), (refNo,r-234), (account,b-123), (unitPrice,15.25), (instrument,ibm)) | |
scala> val trd2 = makeTrade(t2) | |
trd2: Option[net.debasishg.domain.trade.Trades.Trade] = Some(Trade(b-123,ibm,r-234,Singapore,15.25,400)) | |
scala> ((((List(trd1, trd2)) ∘∘ forTrade) ∘∘ taxFees) ∘∘ enrichWith) ∘∘ netAmount | |
res1: List[Option[scala.math.BigDecimal]] = List(Some(3307.5000), Some(8845.0000)) |
Note how the functions
forTrade
, taxFees
etc. get lifted into the List of Options.Another nice feature that becomes extremely useful with scalaz in a domain model is the use of non-breaking error handling. This is made elegant by designing the
Validation[]
abstraction as an applicative functor. You can design your validation functions of the domain model as returning an instance of Validation[]
. They can then be wired together in a variety of ways to implement accumulation of all failures before reporting to the user .. Here's a simple example from the Trade
domain model ..
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// validate trade quantity | |
def validQuantity(qty: BigDecimal): Validation[String, BigDecimal] = | |
try { | |
if (qty <= 0) "qty must be > 0".fail | |
else if (qty > 500) "qty must be <= 500".fail | |
else qty.success | |
} catch { | |
case e => e.toString.fail | |
} | |
// validate unit price | |
def validUnitPrice(price: BigDecimal): Validation[String, BigDecimal] = | |
try { | |
if (price <= 0) "price must be > 0".fail | |
else if (price > 100) "price must be <= 100".fail | |
else price.success | |
} catch { | |
case e => e.toString.fail | |
} | |
// make a trade or report validation failures | |
def makeTrade(account: Account, instrument: Instrument, refNo: String, market: Market, | |
unitPrice: BigDecimal, quantity: BigDecimal) = | |
(validUnitPrice(unitPrice).liftFailNel |@| | |
validQuantity(quantity).liftFailNel) { (u, q) => Trade(account, instrument, refNo, market, u, q) } |
Validation[]
in scalaz works much like Either[], but has a more expressive interface that specifies the success and error types explicitly ..
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
sealed trait Validation[+E, +A] { | |
//.. | |
} | |
final case class Success[E, A](a: A) extends Validation[E, A] | |
final case class Failure[E, A](e: E) extends Validation[E, A] |
You can use
Validation[]
in comprehensions or as an applicative functor and wire up your domain validation logic in a completely functional way. Here's how our above validations work on the REPL ..
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// failure case | |
scala> makeTrade("a-123", "google", "ref-12", Singapore, -10, 600) | |
res2: scalaz.Validation[scalaz.NonEmptyList[String],net.debasishg.domain.trade.Trades.Trade] = Failure(NonEmptyList(price must be > 0, qty must be <= 500)) | |
// success case | |
scala> makeTrade("a-123", "google", "ref-12", Singapore, 10, 200) | |
res3: scalaz.Validation[scalaz.NonEmptyList[String],net.debasishg.domain.trade.Trades.Trade] = Success(Trade(a-123,google,ref-12,Singapore,10,200)) |
When we have invalid trade arguments all validation errors are accumulated and then reported to the user. If all arguments are valid, then we have a valid
Trade
instance as success. Cool stuff .. a recipe that I would like to have as part of my domain modeling everytime I start a new project ..
1 comment:
Excellent stuff. I've been wanting to do something similar for a model of Trades/Products I am working on at the moment. Your blog has given me ideas to get started.
Post a Comment