practical scalaz

41
GSA Capital Practical Scalaz (or) How to make your life easier the hard way Chris Marshall Aug 2011 @oxbow_lakes

Upload: oxbowlakes

Post on 12-May-2015

6.390 views

Category:

Technology


3 download

DESCRIPTION

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

TRANSCRIPT

Page 1: Practical scalaz

GSA Capital

Practical Scalaz

(or)

How to make your life easier the hard way

Chris Marshall Aug 2011@oxbow_lakes

Page 2: Practical scalaz

Overview of talk

The library is confusing What’s with all this maths anyway? The method names are all stupid

GSA Capital

Page 3: Practical scalaz

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

Page 4: Practical scalaz

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

Page 5: Practical scalaz

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   }

Page 6: Practical scalaz

Monoids

There are monoids everywhere A set With an associative operation And an identity under that operation

GSA Capital

Page 7: Practical scalaz

Numbers

scala> 1 |+| 2 res0: Int 3

scala> 1.2 |+| 3.4 res1: Double 4.6

GSA Capital

Page 8: Practical scalaz

Your own

scala> 200.GBP |+| 350.GBP res2: oxbow.Money 550.00 GBP

GSA Capital

Page 9: Practical scalaz

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

Page 10: Practical scalaz

...

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

Page 11: Practical scalaz

What does this mean?

Winning!

GSA Capital

Page 12: Practical scalaz

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

Page 13: Practical scalaz

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

Page 14: Practical scalaz

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

Page 15: Practical scalaz

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

Page 16: Practical scalaz

trait TradingPosition { def sym: Ticker def qty: Int }

type Filter = TradingPosition => Boolean

GSA Capital

Filters

Page 17: Practical scalaz

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)

Page 18: Practical scalaz

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)

Page 19: Practical scalaz

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

Page 20: Practical scalaz

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)

Page 21: Practical scalaz

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...

Page 22: Practical scalaz

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

Page 23: Practical scalaz

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

Page 24: Practical scalaz

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

Page 25: Practical scalaz

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>

Page 26: Practical scalaz

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))

Page 27: Practical scalaz

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

Page 28: Practical scalaz

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

Page 29: Practical scalaz

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

Page 30: Practical scalaz

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

Page 31: Practical scalaz

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

Page 32: Practical scalaz

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]]

Page 33: Practical scalaz

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) => }

Page 34: Practical scalaz

Using for-comprehensions

for { f <- file(“C:/tmp/trades.csv”).liftFailNel

ts <- trades(f) } yield ts

GSA Capital

Page 35: Practical scalaz

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

Page 36: Practical scalaz

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

Page 37: Practical scalaz

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

Page 38: Practical scalaz

Aside: other applicatives

List[Promise[A]].sequence ~> Promise[List[A]]

f: (A, B) => C (Promise[A] |@| Promise[B]) apply f ~> Promise[C]

GSA Capital

Page 39: Practical scalaz

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

Page 40: Practical scalaz

And More

IO Deferring side effects

Writers Logging

Readers Configuration

Typesafe equals Heiko Seeberger at scaladays

GSA Capital

Page 41: Practical scalaz

Great references

Tony Morris’ blog Runar & Mark Harrah’s Apocalisp Nick Partridge’s Deriving Scalaz Jason Zaugg at scala eXchange

GSA Capital