Created
February 15, 2016 20:47
-
-
Save tomtau/eba903d4d7e3d1dd04b5 to your computer and use it in GitHub Desktop.
Cats: FP in Scala (Hong Kong Functional Programming Meetup)
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
{ | |
"cells": [ | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "slide" | |
} | |
}, | |
"source": [ | |
"These is a rough notebook (it was used to generate slides for a short talk at Hong Kong Functional Programming Meetup). A better resource for Cats is the tutorial called [herding cats](http://eed3si9n.com/herding-cats/); for common FP abstractions, [this book](https://www.manning.com/books/functional-programming-in-scala) or [Typeclassopedia](http://typeclassopedia.bitbucket.org) are great references.\n", | |
"# Cats\n", | |
"## A library for functional programming in Scala\n", | |
"### Tomas Tauber (HKU)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 1, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "skip" | |
} | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
":: problems summary ::\n", | |
":::: WARNINGS\n", | |
"\tUnable to reparse com.github.alexarchambault.jupyter#jupyter-scala-api_2.11.6;0.2.0-SNAPSHOT from sonatype-snapshots, using Thu Dec 24 22:50:58 HKT 2015\n", | |
"\n", | |
"\tChoosing sonatype-snapshots for com.github.alexarchambault.jupyter#jupyter-scala-api_2.11.6;0.2.0-SNAPSHOT\n", | |
"\n", | |
"\tUnable to reparse com.github.alexarchambault#ammonite-api_2.11.6;0.3.1-SNAPSHOT from sonatype-snapshots, using Wed Oct 21 17:43:36 HKT 2015\n", | |
"\n", | |
"\tChoosing sonatype-snapshots for com.github.alexarchambault#ammonite-api_2.11.6;0.3.1-SNAPSHOT\n", | |
"\n", | |
"\tUnable to reparse com.github.alexarchambault.jupyter#jupyter-api_2.11;0.2.0-SNAPSHOT from sonatype-snapshots, using Wed Oct 21 23:03:52 HKT 2015\n", | |
"\n", | |
"\tChoosing sonatype-snapshots for com.github.alexarchambault.jupyter#jupyter-api_2.11;0.2.0-SNAPSHOT\n", | |
"\n", | |
":: problems summary ::\n", | |
":::: WARNINGS\n", | |
"\tUnable to reparse com.github.alexarchambault.jupyter#jupyter-scala-api_2.11.6;0.2.0-SNAPSHOT from sonatype-snapshots, using Thu Dec 24 22:50:58 HKT 2015\n", | |
"\n", | |
"\tChoosing sonatype-snapshots for com.github.alexarchambault.jupyter#jupyter-scala-api_2.11.6;0.2.0-SNAPSHOT\n", | |
"\n", | |
"\tUnable to reparse com.github.alexarchambault#ammonite-api_2.11.6;0.3.1-SNAPSHOT from sonatype-snapshots, using Wed Oct 21 17:43:36 HKT 2015\n", | |
"\n", | |
"\tChoosing sonatype-snapshots for com.github.alexarchambault#ammonite-api_2.11.6;0.3.1-SNAPSHOT\n", | |
"\n", | |
"\tUnable to reparse com.github.alexarchambault.jupyter#jupyter-api_2.11;0.2.0-SNAPSHOT from sonatype-snapshots, using Wed Oct 21 23:03:52 HKT 2015\n", | |
"\n", | |
"\tChoosing sonatype-snapshots for com.github.alexarchambault.jupyter#jupyter-api_2.11;0.2.0-SNAPSHOT\n", | |
"\n" | |
] | |
}, | |
{ | |
"data": { | |
"text/plain": [] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"load.compiler.ivy(\"org.scalamacros\" % \"paradise_2.11.6\" % \"2.1.0-M5\")\n", | |
"load.ivy(\"org.typelevel\" %% \"cats\" % \"0.4.1\")" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 2, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"\u001b[32mimport \u001b[36mcats._, cats.std.all._, cats.syntax.eq._\u001b[0m" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"import cats._, cats.std.all._, cats.syntax.eq._" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"source": [ | |
"![Purrfect](http://cdn.meme.am/instances/500x/57053770.jpg)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "slide" | |
} | |
}, | |
"source": [ | |
"#### Agenda\n", | |
"* Motivation / Risk Summary\n", | |
"* Ad-hoc polymorphism with *Typeclasses*\n", | |
"* Operating on \"wrapped\" values with *Functor* and *Applicative*\n", | |
"* Data aggregations with *Monoid*\n", | |
"* Functional error handling with *Option*, *Xor*, and *Validated*" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "slide" | |
} | |
}, | |
"source": [ | |
"#### Motivation\n", | |
"* Different extremes in uses:\n", | |
" * \"*Better Java*\": Java programmer's view of Scala\n", | |
" * \"*Functional language* with a funky, but expressive type system\": Haskeller's view of Scala \n", | |
"* Cats / Scalaz / ...: A collection of common useful abstractions A.K.A. \"FP Design Patterns\"\n", | |
"* (for robust and modular code)\n", | |
"* (intuition is gained after their repeated usage)\n", | |
"* Knowing these abstractions can be useful: [How we used Category Theory to solve a problem in Java](http://techblog.realestate.com.au/how-we-used-category-theory-to-solve-a-problem-in-java/)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"source": [ | |
"##### Cats\n", | |
"* Meant to be \"*nicer*\" (more lightweight, modular, documented...)\n", | |
"* As of now, still quite *experimental* and *not ready* for real deployement (unlike Scalaz with `scalaz-stream`, `Monocle` and other great stuff)\n", | |
"* *BUT*: it's a good educational material\n", | |
"* *ALSO*: some abstractions are shared / overlapping among different libraries (such as Spire and Algebird) \n", | |
"* *AND*: some applications are appearing (such as [iteratee.io](https://github.com/travisbrown/iteratee) or [circe](https://github.com/travisbrown/circe) for JSON)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"source": [ | |
"#### Risk Summary (all libraries in general, not only Cats)\n", | |
"* *Safe zone*: numerical computing, batch and stream processing pipelines (e.g. [Algebird and Spark](http://esumitra.github.io/algebird-boston-spark/#/))\n", | |
"* *Middle ground* (potential costs of adaptation): error handling, parsing, and many other libraries for pure FP\n", | |
"* *Danger zone*: [modern FP architecture using free structures](http://degoes.net/articles/modern-fp/)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "slide" | |
} | |
}, | |
"source": [ | |
"#### Ad-hoc polymorphism with typeclasses\n", | |
"I assume everyone is familiar with parametric polymorphism:" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 3, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"defined \u001b[32mfunction \u001b[36mmap\u001b[0m" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"def map[A,B](f: A=>B, l: List[A]): List[B] = l.map(f)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"source": [ | |
"* In Haskell, we can put additional *contraints* on these parametric types denoting required operations (e.g. `Ord` for `compare` operation):\n", | |
"```haskell\n", | |
"sort :: Ord a => [a] -> [a]\n", | |
"```\n", | |
"* A bit like Java interfaces, but\n", | |
" 1. Without `this` to refer to an associated object\n", | |
" 2. Haskell type class instances (implementations) are completely separate from its associated type\n", | |
" 3. Before Java 8, interfaces couldn't have default implementations\n", | |
"* Solution to many software extension and integration problems\n", | |
"* In Scala, we can emulate type classes with traits + implicit objects\n", | |
"* Nowadays (as done in Cats), we can write type classes can without much boilerplate ([simulacrum](https://github.com/mpilquist/simulacrum)) and at low cost ([machinist](https://github.com/typelevel/machinist))" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 4, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"\u001b[32mimport \u001b[36mcats.syntax.order._\u001b[0m" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"import cats.syntax.order._" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 5, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"\u001b[36mres4\u001b[0m: \u001b[32mInt\u001b[0m = \u001b[32m-1\u001b[0m" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"1.0 compare 2.0" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 6, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"outputs": [ | |
{ | |
"ename": "", | |
"evalue": "", | |
"output_type": "error", | |
"traceback": [ | |
"Compilation Failed", | |
"\u001b[31mMain.scala:297: type mismatch;", | |
" found : Double(2.0)", | |
" required: Int", | |
"1 compare 2.0", | |
" ^\u001b[0m" | |
] | |
} | |
], | |
"source": [ | |
"1 compare 2.0" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"source": [ | |
"Under the hood, we got in implicit implementation of:\n", | |
"```scala\n", | |
"trait Order[@sp A] extends Any with PartialOrder[A] { self =>\n", | |
"\n", | |
" /** [...]\n", | |
" */\n", | |
" def compare(x: A, y: A): Int\n", | |
"\n", | |
" ...\n", | |
"```\n", | |
"with\n", | |
"```scala\n", | |
"trait IntInstances {\n", | |
" implicit val intAlgebra = new IntAlgebra\n", | |
"\n", | |
" val IntMinMaxLattice: BoundedDistributiveLattice[Int] =\n", | |
" BoundedDistributiveLattice.minMax[Int](Int.MinValue, Int.MaxValue)(intAlgebra)\n", | |
"}\n", | |
"\n", | |
"class IntAlgebra extends EuclideanRing[Int]\n", | |
" with Order[Int] with Serializable {\n", | |
"\n", | |
" def compare(x: Int, y: Int): Int =\n", | |
" if (x < y) -1 else if (x > y) 1 else 0\n", | |
" ...\n", | |
"```\n", | |
"\n", | |
"Automated instances can be derived through [kittens](https://github.com/milessabin/kittens)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "slide" | |
} | |
}, | |
"source": [ | |
"#### Functor and Applicative\n", | |
"[Source of pictures](http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html)\n", | |
"##### Functor\n", | |
"For things we can \"map\" over, this is its type class:\n", | |
"```scala\n", | |
"@typeclass trait Functor[F[_]] extends functor.Invariant[F] { self =>\n", | |
" def map[A, B](fa: F[A])(f: A => B): F[B]\n", | |
"\n", | |
" ....\n", | |
"}\n", | |
"```\n", | |
"\n", | |
"Let's look at `Option`:\n", | |
"```scala\n", | |
" sealed trait Option[+A] {\n", | |
" ...\n", | |
" }\n", | |
" object None extends Option[Nothing] {\n", | |
" ...\n", | |
" }\n", | |
" case class Some[+A](x: A) extends Option[A] {\n", | |
" ...\n", | |
" }\n", | |
"```" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 7, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"\u001b[36mres5\u001b[0m: \u001b[32mOption\u001b[0m[\u001b[32mInt\u001b[0m] = Some(5)" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"Some(2) map {_ + 3}" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"source": [ | |
"![Option functor 1](http://adit.io/imgs/functors/fmap_just.png)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 8, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"\u001b[36mres6\u001b[0m: \u001b[32mOption\u001b[0m[\u001b[32mInt\u001b[0m] = None" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"(None: Option[Int]) map {_ + 3}" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"source": [ | |
"![Option functor 2](http://adit.io/imgs/functors/fmap_nothing.png)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 9, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"\u001b[36mres7\u001b[0m: \u001b[32mList\u001b[0m[\u001b[32mInt\u001b[0m] = \u001b[33mList\u001b[0m(\u001b[32m5\u001b[0m, \u001b[32m7\u001b[0m, \u001b[32m9\u001b[0m)" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"List(2,4,6) map {_ + 3}" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"source": [ | |
"![Functor list](http://adit.io/imgs/functors/fmap_list.png)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 10, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"\u001b[32mimport \u001b[36mcats.syntax.functor._\u001b[0m" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"import cats.syntax.functor._" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 11, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"\u001b[36mfoo\u001b[0m: \u001b[32mInt\u001b[0m => \u001b[32mInt\u001b[0m = <function1>" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"val foo = ((x: Int) => x + 3) map {_ + 2}" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 12, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"\u001b[36mres10\u001b[0m: \u001b[32mInt\u001b[0m = \u001b[32m15\u001b[0m" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"foo(10)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"source": [ | |
"![Function functor](http://adit.io/imgs/functors/fmap_function.png)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "slide" | |
} | |
}, | |
"source": [ | |
"##### Apply and Applicative\n", | |
"What if we want this apply function from `Some((_:Int)+3)` to Some(2)?\n", | |
"\n", | |
"```scala\n", | |
"trait Apply[F[_]] extends Functor[F] with ApplyArityFunctions[F] { self =>\n", | |
" /** [...]\n", | |
" */\n", | |
" def ap[A, B](fa: F[A])(f: F[A => B]): F[B]\n", | |
"\n", | |
" ....\n", | |
"}\n", | |
"@typeclass trait Applicative[F[_]] extends Apply[F] { self =>\n", | |
" /** [...]\n", | |
" */\n", | |
" def pure[A](x: A): F[A]\n", | |
"\n", | |
" ....\n", | |
"}\n", | |
"```" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 13, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"\u001b[32mimport \u001b[36mcats.syntax.apply._\u001b[0m" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"import cats.syntax.apply._" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 14, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"defined \u001b[32mclass \u001b[36mIdOp\u001b[0m\n", | |
"defined \u001b[32mfunction \u001b[36mnone\u001b[0m" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
" implicit class IdOp[A](val a: A) {\n", | |
" def some: Option[A] = Some(a)\n", | |
" }\n", | |
" def none[A]: Option[A] = None" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 15, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"\u001b[36mres13\u001b[0m: \u001b[32mOption\u001b[0m[\u001b[32mInt\u001b[0m] = Some(5)" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"{{(_: Int) + 3}.some } ap (2.some)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"source": [ | |
"![Applicative](http://adit.io/imgs/functors/applicative_just.png)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 16, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"\u001b[36mres14\u001b[0m: \u001b[32mList\u001b[0m[\u001b[32mInt\u001b[0m] = \u001b[33mList\u001b[0m(\u001b[32m2\u001b[0m, \u001b[32m4\u001b[0m, \u001b[32m6\u001b[0m, \u001b[32m4\u001b[0m, \u001b[32m5\u001b[0m, \u001b[32m6\u001b[0m)" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"{List({(_: Int) * 2},{(_: Int) + 3}) } ap (List(1,2,3))" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"source": [ | |
"![Applicative list](http://adit.io/imgs/functors/applicative_list.png)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 17, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "slide" | |
} | |
}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"\u001b[32mimport \u001b[36mcats.syntax.cartesian._\u001b[0m" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"import cats.syntax.cartesian._" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"source": [ | |
"We import this to get syntax sugar for operating on several Applicative values using the infamous `|@|` AKA \"Chelsea bun\" AKA \"Admiral Ackbar\" AKA \"The Scream\" AKA \"Macaulay Culkin\" AKA \"Home Alone\" operator:\n", | |
"![|@|](http://athenacinema.com/wp-content/uploads/2013/11/Home_Alone_Boy1.jpg)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"source": [ | |
"We can then easily operate on multiple arguments" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 18, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"\u001b[36mres16\u001b[0m: \u001b[32mOption\u001b[0m[\u001b[32mInt\u001b[0m] = Some(-2)" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"(3.some |@| 5.some) map { _ - _ }" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 19, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"\u001b[36mres17\u001b[0m: \u001b[32mOption\u001b[0m[\u001b[32mList\u001b[0m[\u001b[32mInt\u001b[0m]] = Some(List(3, 4))" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"(3.some |@| List(4).some) map { _ :: _ }" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "slide" | |
} | |
}, | |
"source": [ | |
"#### Data aggregations with *Monoid*\n", | |
"Algebraic structure that can combine two things together and has an identity element.\n", | |
"```scala\n", | |
"trait Semigroup[@sp(Int, Long, Float, Double) A] extends Any with Serializable {\n", | |
"\n", | |
" /**[...]\n", | |
" */\n", | |
" def combine(x: A, y: A): A\n", | |
"...\n", | |
"trait Monoid[@sp(Int, Long, Float, Double) A] extends Any with Semigroup[A] {\n", | |
"\n", | |
" /**\n", | |
" * Return the identity element for this monoid.\n", | |
" */\n", | |
" def empty: A\n", | |
"...\n", | |
"```" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 20, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "slide" | |
} | |
}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"\u001b[32mimport \u001b[36mcats.syntax.semigroup._\u001b[0m" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"import cats.syntax.semigroup._" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"source": [ | |
"`|+|` is shorthand for `combine`" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 21, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"\u001b[36mres19\u001b[0m: \u001b[32mInt\u001b[0m = \u001b[32m4\u001b[0m" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"1 |+| 3" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 22, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"\u001b[36mres20\u001b[0m: \u001b[32mList\u001b[0m[\u001b[32mInt\u001b[0m] = \u001b[33mList\u001b[0m(\u001b[32m1\u001b[0m, \u001b[32m2\u001b[0m, \u001b[32m3\u001b[0m, \u001b[32m4\u001b[0m)" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"List(1,2) |+| List(3,4)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 23, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"\u001b[36mres21\u001b[0m: \u001b[32mString\u001b[0m = \u001b[32m\"Hong Kong\"\u001b[0m" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"\"Hong \" |+| \"Kong\"" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 24, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"\u001b[36mres22\u001b[0m: \u001b[32mOption\u001b[0m[\u001b[32mInt\u001b[0m] = Some(3)" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"1.some |+| 2.some" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 25, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"\u001b[36mres23\u001b[0m: \u001b[32mOption\u001b[0m[\u001b[32mInt\u001b[0m] = Some(1)" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"1.some |+| none[Int]" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 26, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"\u001b[36mres24\u001b[0m: \u001b[32mOption\u001b[0m[\u001b[32mInt\u001b[0m] = Some(2)" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"none[Int] |+| 2.some" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 27, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"\u001b[36mres25\u001b[0m: \u001b[32mOption\u001b[0m[\u001b[32mInt\u001b[0m] = None" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"none[Int] |+| none[Int]" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 28, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"\u001b[36mres26\u001b[0m: \u001b[32mInt\u001b[0m = \u001b[32m6\u001b[0m" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
" Foldable[List].fold(List(1, 2, 3))(Monoid.additive[Int])" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "slide" | |
} | |
}, | |
"source": [ | |
"##### Algebraic Properties\n", | |
"* *Associative* a+(b+c) = (a+b)+c: can be partitioned/run in parallel\n", | |
"* *Closed* (Monoid + Monoid = Monoid): reduce in multiple stages\n", | |
"* *Identity*: support for zero/empty elements\n", | |
"\n", | |
"[Cats on Spark](https://github.com/non/big-electric-cat)\n", | |
"```scala\n", | |
" implicit class rddOps[A: ClassTag](lhs: RDD[A]) {\n", | |
" def csum(implicit m: Monoid[A]): A = lhs.reduce(_ |+| _)\n", | |
" def cmin(implicit o: Order[A]): A = lhs.reduce(_ min _)\n", | |
" def cmax(implicit o: Order[A]): A = lhs.reduce(_ max _)\n", | |
" }\n", | |
" ...\n", | |
"```\n", | |
"\n", | |
"##### Other Monoids\n", | |
"* `Map` and `Set`\n", | |
"* Top k elements\n", | |
"* Bloom filter\n", | |
"* count-min sketch, HyperLogLog, stochastic gradient descent, moments of distributions..." | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "slide" | |
} | |
}, | |
"source": [ | |
"#### Functional error handling with *Option*, *Xor*, and *Validated*" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"source": [ | |
"* Java error throwing and `null` (can be wrapped in *Try*)\n", | |
"* We'll look at more FP-friendly variants" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 29, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"defined \u001b[32mfunction \u001b[36mhalf\u001b[0m" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"def half(x: Int): Option[Int] = \n", | |
" if (x % 2 == 0) Some(x / 2)\n", | |
" else None" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"source": [ | |
"![half](http://adit.io/imgs/functors/half.png)\n", | |
"But a wrapped value?\n", | |
"![half wrapped](http://adit.io/imgs/functors/half_ouch.png)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"source": [ | |
"need to use `flatMap`:\n", | |
"![flatmap](http://adit.io/imgs/functors/plunger.jpg)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 30, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"\u001b[36mres28\u001b[0m: \u001b[32mOption\u001b[0m[\u001b[32mInt\u001b[0m] = None" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"Some(3) flatMap half" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"source": [ | |
"![Some monad](http://adit.io/imgs/functors/monad_just.png)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 31, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"\u001b[36mres29\u001b[0m: \u001b[32mOption\u001b[0m[\u001b[32mInt\u001b[0m] = Some(2)" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"Some(4) flatMap half" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 32, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"\u001b[36mres30\u001b[0m: \u001b[32mOption\u001b[0m[\u001b[32mInt\u001b[0m] = None" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"None flatMap half" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"source": [ | |
"![Nothing](http://adit.io/imgs/functors/monad_nothing.png)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"source": [ | |
"```scala\n", | |
"@typeclass trait FlatMap[F[_]] extends Apply[F] {\n", | |
" def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B]\n", | |
" // AKA bind -- its operator shorthand is >>=\n", | |
" ....\n", | |
"}\n", | |
"```\n", | |
"![flatMap](http://adit.io/imgs/functors/bind_def.png)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 33, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"\u001b[32mimport \u001b[36mcats.syntax.flatMap._\u001b[0m" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"import cats.syntax.flatMap._" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 34, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"\u001b[36mres32\u001b[0m: \u001b[32mOption\u001b[0m[\u001b[32mInt\u001b[0m] = None" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"20.some >>= half >>= half >>= half" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "slide" | |
} | |
}, | |
"source": [ | |
"![monad chain](http://adit.io/imgs/functors/monad_chain.png)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 35, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "slide" | |
} | |
}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"\u001b[36mres33\u001b[0m: \u001b[32mOption\u001b[0m[\u001b[32mInt\u001b[0m] = None" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"for {\n", | |
" x0 <- Some(20)\n", | |
" x1 <- half(x0)\n", | |
" x2 <- half(x1)\n", | |
" x3 <- half(x2)\n", | |
"} yield x3" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 36, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "slide" | |
} | |
}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"defined \u001b[32mclass \u001b[36mUser\u001b[0m\n", | |
"defined \u001b[32mobject \u001b[36mUser\u001b[0m" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"final class User private(val name: String, val age: Int, val email: String)\n", | |
"\n", | |
"object User {\n", | |
" def isValidEmail(email : String): Option[String] = \"\"\"^[-a-z0-9!#$%&'*+/=?^_`{|}~]+(\\.[-a-z0-9!#$%&'*+/=?^_`{|}~]+)*@([a-z0-9]([-a-z0-9]{0,61}[a-z0-9])?\\.)*(aero|arpa|asia|biz|cat|com|coop|edu|gov|info|int|jobs|mil|mobi|museum|name|net|org|pro|tel|travel|[a-z][a-z])$\"\"\".r.findFirstIn(email) \n", | |
" def isValidAge(age: Int): Option[Int] = if (age >= 0) Some(age) else None\n", | |
" def isValidName(name: String): Option[String] = if (name.length > 0) Some(name) else None\n", | |
" \n", | |
" def create(name: String, age: Int, email: String): Option[User] =\n", | |
" for {\n", | |
" vname <- isValidName(name)\n", | |
" vage <- isValidAge(age)\n", | |
" vemail <- isValidEmail(email)\n", | |
" } yield (new User(vname,vage,vemail))\n", | |
"}" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 37, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"\u001b[36mu1\u001b[0m: \u001b[32mOption\u001b[0m[\u001b[32mString\u001b[0m] = None\n", | |
"\u001b[36mu2\u001b[0m: \u001b[32mOption\u001b[0m[\u001b[32mString\u001b[0m] = Some(Li Ka-shing)" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"val u1 = User.create(\"\",-1,\"\") map {_.name}\n", | |
"val u2 = User.create(\"Li Ka-shing\",87,\"nospam@lksf.org\") map {_.name}" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"source": [ | |
"Fine when there's one type of error.\n", | |
"\n", | |
"But we have three types of errors:" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 38, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"defined \u001b[32mclass \u001b[36mUserError\u001b[0m\n", | |
"defined \u001b[32mobject \u001b[36mEmptyName\u001b[0m\n", | |
"defined \u001b[32mclass \u001b[36mInvalidEmail\u001b[0m\n", | |
"defined \u001b[32mclass \u001b[36mInvalidAge\u001b[0m" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"sealed abstract class UserError\n", | |
"case object EmptyName extends UserError\n", | |
"case class InvalidEmail(s: String) extends UserError\n", | |
"case class InvalidAge(age: Int) extends UserError" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"source": [ | |
"We could use `Either`,\n", | |
"but standard library's `Either` wouldn't work with fors\n", | |
"(doesn't have flatMap).\n", | |
"For that, Cats have a right-biased Either AKA Xor" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 39, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"\u001b[32mimport \u001b[36mcats.data._\u001b[0m\n", | |
"\u001b[32mimport \u001b[36mcats.data.Xor._\u001b[0m" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"import cats.data._\n", | |
"import cats.data.Xor._" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 40, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"defined \u001b[32mclass \u001b[36mUser2\u001b[0m\n", | |
"defined \u001b[32mobject \u001b[36mUser2\u001b[0m" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"final class User2 private(val name: String, val age: Int, val email: String)\n", | |
"\n", | |
"object User2 {\n", | |
" def isValidEmail(email : String): UserError Xor String = if (\"\"\"^[-a-z0-9!#$%&'*+/=?^_`{|}~]+(\\.[-a-z0-9!#$%&'*+/=?^_`{|}~]+)*@([a-z0-9]([-a-z0-9]{0,61}[a-z0-9])?\\.)*(aero|arpa|asia|biz|cat|com|coop|edu|gov|info|int|jobs|mil|mobi|museum|name|net|org|pro|tel|travel|[a-z][a-z])$\"\"\".r.findFirstIn(email) == None) left(InvalidEmail(email)) else right(email) \n", | |
" def isValidAge(age: Int): UserError Xor Int = if (age >= 0) right(age) else left(InvalidAge(age))\n", | |
" def isValidName(name: String): UserError Xor String = if (name.length > 0) right(name) else left(EmptyName)\n", | |
" \n", | |
" def create(name: String, age: Int, email: String): UserError Xor User2 =\n", | |
" for {\n", | |
" vname <- isValidName(name)\n", | |
" vage <- isValidAge(age)\n", | |
" vemail <- isValidEmail(email)\n", | |
" } yield (new User2(vname,vage,vemail))\n", | |
"}" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 41, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"\u001b[36mu1e\u001b[0m: \u001b[32mXor\u001b[0m[\u001b[32mUserError\u001b[0m, \u001b[32mString\u001b[0m] = Left(EmptyName)\n", | |
"\u001b[36mu2e\u001b[0m: \u001b[32mXor\u001b[0m[\u001b[32mUserError\u001b[0m, \u001b[32mString\u001b[0m] = Right(Li Ka-shing)" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"val u1e = User2.create(\"\",-1,\"\") map {_.name}\n", | |
"val u2e = User2.create(\"Li Ka-shing\",87,\"nospam@lksf.org\") map {_.name}" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"source": [ | |
"`Option` and `Xor` are \"flatmappable\" (monads) in a chain -- after a failure, they shortcircuit.\n", | |
"We may want to process all events.\n", | |
"For that, there's `Validated` which is an applicative functor." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 42, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"\u001b[32mimport \u001b[36mcats.data.Validated\u001b[0m\n", | |
"\u001b[32mimport \u001b[36mValidated.{ valid, invalid }\u001b[0m" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"import cats.data.Validated\n", | |
"import Validated.{ valid, invalid }" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 43, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"\u001b[36mres41\u001b[0m: \u001b[32mValidated\u001b[0m[\u001b[32mString\u001b[0m, \u001b[32mString\u001b[0m] = Invalid(event 2 failed!event 3 failed!)" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"(valid[String, String](\"event 1 ok\") |@|\n", | |
"invalid[String, String](\"event 2 failed!\") |@|\n", | |
"invalid[String, String](\"event 3 failed!\")) map {_ + _ + _}" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"source": [ | |
"Cats also provided `NonEmptyList` which will be useful for accumulating errors:" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 44, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"\u001b[32mimport \u001b[36mcats.data.{ NonEmptyList => NEL }\u001b[0m" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"import cats.data.{ NonEmptyList => NEL }" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 45, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"defined \u001b[32mclass \u001b[36mUser3\u001b[0m\n", | |
"defined \u001b[32mobject \u001b[36mUser3\u001b[0m" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"final class User3 private(val name: String, val age: Int, val email: String)\n", | |
"\n", | |
"object User3 {\n", | |
" def isValidEmail(email : String): Validated[NEL[UserError], String] = if (\"\"\"^[-a-z0-9!#$%&'*+/=?^_`{|}~]+(\\.[-a-z0-9!#$%&'*+/=?^_`{|}~]+)*@([a-z0-9]([-a-z0-9]{0,61}[a-z0-9])?\\.)*(aero|arpa|asia|biz|cat|com|coop|edu|gov|info|int|jobs|mil|mobi|museum|name|net|org|pro|tel|travel|[a-z][a-z])$\"\"\".r.findFirstIn(email) == None) invalid(NEL(InvalidEmail(email))) else valid(email) \n", | |
" def isValidAge(age: Int): Validated[NEL[UserError], Int] = if (age >= 0) valid(age) else invalid(NEL(InvalidAge(age)))\n", | |
" def isValidName(name: String): Validated[NEL[UserError], String] = if (name.length > 0) valid(name) else invalid(NEL(EmptyName))\n", | |
" \n", | |
" def create(name: String, age: Int, email: String): Validated[NEL[UserError], User3] =\n", | |
" (isValidName(name) |@|\n", | |
" isValidAge(age) |@|\n", | |
" isValidEmail(email)) map {new User3(_, _, _)}\n", | |
"} " | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 48, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"\u001b[36mu1v\u001b[0m: \u001b[32mValidated\u001b[0m[\u001b[32mNonEmptyList\u001b[0m[\u001b[32mUserError\u001b[0m], \u001b[32mString\u001b[0m] = Invalid(OneAnd(EmptyName,List(InvalidAge(-1), InvalidEmail())))\n", | |
"\u001b[36mu2v\u001b[0m: \u001b[32mValidated\u001b[0m[\u001b[32mNonEmptyList\u001b[0m[\u001b[32mUserError\u001b[0m], \u001b[32mString\u001b[0m] = Valid(Li Ka-shing)" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"val u1v = User3.create(\"\",-1,\"\") map {_.name}\n", | |
"val u2v = User3.create(\"Li Ka-shing\",87,\"nospam@lksf.org\") map {_.name}" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"source": [ | |
"There's also `Ior` which is a hybrid between `Xor` and `Validated` -- example usage:\n", | |
"* Accumulate warnings\n", | |
"* Shortcircuit on errors\n", | |
"\n", | |
"Anyway, we stop here for now\n", | |
"even though\n", | |
"there are other interesting things in Cats (and similar libraries) " | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "slide" | |
} | |
}, | |
"source": [ | |
"#### Summary\n", | |
"* A collection of useful abstractions for FP\n", | |
"* Extensible design with typeclasses\n", | |
"* Operating on wrapped values:\n", | |
"![recap](http://adit.io/imgs/functors/recap.png)\n", | |
"* \"Made-for-parallelism\" Monoids\n", | |
"* Functional error handling:\n", | |
" * `Option` for single-error shortcircuit\n", | |
" * `Xor` for multiple-error shortcircuit\n", | |
" * `Validated` for multiple-error log" | |
] | |
} | |
], | |
"metadata": { | |
"celltoolbar": "Slideshow", | |
"kernelspec": { | |
"display_name": "Scala 2.11", | |
"language": "scala211", | |
"name": "scala211" | |
}, | |
"language_info": { | |
"codemirror_mode": "text/x-scala", | |
"file_extension": ".scala", | |
"mimetype": "text/x-scala", | |
"name": "scala211", | |
"pygments_lexer": "scala", | |
"version": "2.11.6" | |
} | |
}, | |
"nbformat": 4, | |
"nbformat_minor": 0 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment