practical scalaz
DESCRIPTION
Slides from a presentation entitles "Practical Scalaz" given to the London Scala User Group (lsug) at Skillsmatter on Sept 14th 2011TRANSCRIPT
GSA Capital
Practical Scalaz
(or)
How to make your life easier the hard way
Chris Marshall Aug 2011@oxbow_lakes
Overview of talk
The library is confusing What’s with all this maths anyway? The method names are all stupid
GSA Capital
Where is the love?
Kinds M[A] ~> MA[M, A] A ~> Identity[A] M[A, B] ~> MAB[M, A, B]
Wrappers OptionW, ListW, BooleanW
Data Types Validation NonEmptyList
GSA Capital
Typeclasses
Common “patterns” “retrofitted” in a uniform way to many types Uses implicits to do this
...Interfaces Like being able to retrofit an interface onto
classes which “logically implement” that interface
...Adapters Adapt existing types to our structures
GSA Capital
Example typeclass
trait Each[-E[_]] { def each[A](e: E[A], f: A => Unit): Unit
}
GSA Capital
implicit def OptionEach: Each[Option] = new Each[Option] { def each[A](e: Option[A], f: A => Unit)
= e foreach f }
Monoids
There are monoids everywhere A set With an associative operation And an identity under that operation
GSA Capital
Numbers
scala> 1 |+| 2 res0: Int 3
scala> 1.2 |+| 3.4 res1: Double 4.6
GSA Capital
Your own
scala> 200.GBP |+| 350.GBP res2: oxbow.Money 550.00 GBP
GSA Capital
Monoids Beget Monoids
Option[A] is a Monoid if A is a monoid
(A, B, .. N) is a Monoid if A, B..N are monoids
A => B is a Monoid if B is a Monoid
Map[A, B] is a Monoid if B is a Monoid
A => A is a monoid Under function composition
GSA Capital
...
scala> some(4) |+| none[Int]res4: Option[Int] Some(4)
scala> none[Int] |+| none[Int]res5: Option[Int] None
scala> some(4) |+| some(5)res6: Option[Int] Some(9)
scala> (1, “a”, 4.5) |+| (2, “b”, 3.2)res7: (Int, String, Double) (3, “ab”, 7.7)
GSA Capital
What does this mean?
Winning!
GSA Capital
trait TradingPosition { def inventoryPnL(implicit prices: Map[Ticker, Double]) : Double def tradingPnL(implicit prices: Map[Ticker, Double]) : Double final def totalPnL(implicit prices: Map[Ticker, Double]) = inventoryPnL -> tradingPnL }
GSA Capital
val positions: Seq[TradingPosition] = db.latestPositions() val (totalTrad, totalInv) = positions.map(_.totalPnL).asMA.sum
trait TradingPosition {
def inventoryPnL(implicit pxs: Map[Ticker, Double]): Option[Double] def tradingPnL(implicit pxs: Map[Ticker, Double]): Option[Double] final def totalPnL(implicit pxs: Map[Ticker, Double]) = inventoryPnL |+| tradingPnL }
GSA Capital
val posns: Seq[TradingPosition] = db.latestPositions() val maybePnL: Option[Double] = posns.map(_.totalPnL).asMA.sum
trait TradingPosition { def sym: Ticker def qty: Int }
GSA Capital
val pete: Map[Ticker, Int] = positions1.map(p => p.sym -> p.qty).toMap val fred: Map[Ticker, Int] = positions2.map(p => p.sym -> p.qty).toMap
val totalPositions = pete |+| fred
GSA Capital
for any key, if book1(key) == n and book2(key) == m, then the resulting map has n |+| m at key
Adding across a bunch of Maps now becomes as easy as...
allBooks.asMA.sum
trait TradingPosition { def sym: Ticker def qty: Int }
type Filter = TradingPosition => Boolean
GSA Capital
Filters
Observe: filters are monoids
GSA Capital
val london: Filter = (_ : TradingPositon).sym.id endsWith “.L” val ny: Filter = (_ : TradingPositon).sym.id endsWith “.O”
positions filter (london |+| ny)
Conjunction
type Filter = TradingPosition => BooleanConjunction
GSA Capital
val london = (t : TradingPositon) => (t.sym.id endsWith “.L”) | |∧ val big = (t : TradingPositon) => (t.qty > 100000) | |∧
positions filter (london |+| big)
Monoid = Semigroup + Zero
Monoid is actually split in 2 Semigroup (the associative bit) Zero (the identity bit)
~ is “or zero” on OptionW A unary method (declared unary_~)
It is really useful Eh?
GSA Capital
var posns: Map[Ticker, Int] = Map.empty
def newTrade(trd: Trade) { posns += (trd.sym -> ( (posns.get(trd.sym) getOrElse 0) + trd.qty)) }
GSA Capital
But observe the equivalence of the following:
(posns.get(trd.sym) getOrElse 0)
And:
~posns.get(trd.sym)
GSA Capital
def newTrade(trd: Trade) { posns += (trd.sym -> (~posns.get(trd.sym) |+| trd.qty)) }
We can change the value type To Double?
To any Monoid!
Your own?
Is logically...
var charges: Map[Ticker, Money] = Map.empty implicit val ChargeCcy = Currency.USD
def newTrade(trd: Trade) { charges += (trd.sym -> ( ~charges.get(trd.sym) |+| trd.charges)) }
Where we have defined our own thus
implicit def MoneyZero(implicit ccy: Currency) : Zero[Money] = zero(Money.zero(ccy))
implicit val MoneySemigroup: Semigroup[Money] = semigroup(_ add _)
GSA Capital
BooleanW, OptionW
Consistency with ? and | Option[A] | A == getOrElse Boolean ? a | b == ternary Boolean ?? A == raise into zero Boolean !? A == same same but different Boolean guard / prevent
GSA Capital
Endo
An Endo in scalaz is just a function: A => A It’s a translation (e.g. negation)
BooleanW plus Zero[Endo] “If this condition holds, apply this transformation”
GSA Capital
We don’t want to repeat ourselves!
for { e <- xml \ “instruments” f <- e.attribute(“filter”) } yield (if (f == “incl”) new Filter(instr(e)) else new Filter(instr(e)).neg) ^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^
GSA Capital
<instruments filter=“incl”> <symbol value=“VOD.L” /> <symbol value=“MSFT.O” /> </instruments>
We can do this...
GSA Capital
val reverseFilter = EndoTo((_ : Filter).neg) for {
e <- xml \ “instruments”f <- e.attribute(“filter”)
} yield (f == “incl”) !? reverseFilter apply new
Filter(instr(e))
Aside: playing around
scala> EndoTo(-(_ : Double)) res0: scalaz.Endo[Double] scalaz.Endo@6754642
scala> true ?? res0 apply 2.3 res1: Double -2.3 scala> false ?? res0 apply 2.3 res2: Double 2.3
scala> implicitly[Zero[Endo[Double]]] res3: scalaz.Endo[Double] scalaz.Endo@8ae765
GSA Capital
Validation
Validation is the killer app for me Opened my eyes to how appalling
java Exceptions are Let your types do the talking!
GSA Capital
Aside: composition
Functors: M[A] plus A => B equals M[B]
Monads M[A] plus A => M[B] equals M[B]
Applicative M[A] plus M[A => B] equals M[B]
GSA Capital
Composing validations
//MAPValidation[X, A] ~> A => B ~> Validation[X, B]
//FLATMAPValidation[X, A] ~> A => Validation[X, B] ~> Validation[X,
B]
//APPLYValidation[X1, A], Validation[X2, B] ~> (A, B) => C ~> Validation[X1 |+| X2, C]
GSA Capital
ValidationNEL
Validation[NonEmptyList[F], S] = ValidationNEL[F, S]
scala> “Bah!”.failNel[Int]res1 : scalaz.Validation[NonEmptyList[String], Int]
Failure(NonEmptyList(Bah!))
scala> 1.successNel[String]res2 : scalaz.Validation[NonEmptyList[String], Int]
Success(1)
GSA Capital
Using Validation
def file(s: String) : Validation[String, File]
def trades(file: File): List[Trade]
GSA Capital
val ts = file(“C:/tmp/trades.csv”) map trades
//ts of type Validation[String, List[Trade]]
More realistically
def trades(f: File): ValidationNEL[String,
List[Trade]]
GSA Capital
file(“C:/tmp/trades.csv”).liftFailNel flatMap trades match {
case Failure(msgs) => case Success(trades) => }
Using for-comprehensions
for { f <- file(“C:/tmp/trades.csv”).liftFailNel
ts <- trades(f) } yield ts
GSA Capital
What does trades look like?
def trades(f: File): ValidationNEL[String, Trade] = {
//List[String] val ls = io.Source.fromFile(f).getLines().toList
def parse(line: String): Validation[String, Trade]
= sys.error(“TODO”)
ls map parse <<SOMETHING with List[Validation[String, Trade]]>>
}
GSA Capital
What does trades look like?
def trades(f: File): ValidationNEL[String, List[Trade]] = {
//List[String] val ls = io.Source.fromFile(f).getLines().toList
def parse(line: String): Validation[String, Trade]
= sys.error(“TODO”)
(ls map (l => parse(l).liftFailNel)) .sequence[({type l[a]=ValidationNEL[String, a]})#l,
Trade]
}GSA Capital
So why do I care?
Your program logic is no longer forked Catching exceptions Throwing exceptions
Ability to compose Via map, flatMap and applicative Keeps signatures simple
Accumulate errors
GSA Capital
Aside: other applicatives
List[Promise[A]].sequence ~> Promise[List[A]]
f: (A, B) => C (Promise[A] |@| Promise[B]) apply f ~> Promise[C]
GSA Capital
Far too much stuff
Iteratees Kleisli
F: A => M[B] If M is a functor and I have g : B => C, then I
should be able to compose these If M is a Monad and I have h : B => M[C] etc
Arrows Useful methods for applying functions across
data structures, like pairs
GSA Capital
And More
IO Deferring side effects
Writers Logging
Readers Configuration
Typesafe equals Heiko Seeberger at scaladays
GSA Capital
Great references
Tony Morris’ blog Runar & Mark Harrah’s Apocalisp Nick Partridge’s Deriving Scalaz Jason Zaugg at scala eXchange
GSA Capital