Practical scalaz

Post on 12-May-2015

6,388 views 3 download

Tags:

description

Slides from a presentation entitles "Practical Scalaz" given to the London Scala User Group (lsug) at Skillsmatter on Sept 14th 2011

transcript

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