en nybegynners introduksjon til scalaz
DESCRIPTION
My presentation at Javazone 2013 http://jz13.java.no/presentation.html?id=6c09d5d7TRANSCRIPT
© Mesan AS
scalaz
En nybegynners introduksjon
© Mesan AS
Agenda•Bakgrunnen for at jeg har tatt i bruk scalaz
•En vei gjennom rammeverket
•Konklusjoner
•Gjerne spørsmål underveis!
© Mesan AS
Trond Marius Øvstetun
•Sjesfkonsulent i Mesan
•Utvikler, arkitekt, teamleder +++
© Mesan AS
Mesan... når standardsystemer ikke er nok
•Systemutvikling – skreddersøm
•Løsningsfokus
•Transaksjonssystemer
•Lang levetid – langsiktighet ogvedlikeholdbarhet
© Mesan AS
Hvorfor scalaz?
© Mesan AS
Hvorfor scalaz?•If you are thinking about using Scalaz, stop now while you still have your sanity! ref
•Don’t listen to anyone telling you to use Scalaz!
•Hva er greia med metodene<:::, :::>, <+>, |@|, ★, ☆, <=< ?
© Mesan AS
Mitt triggerpunkt
© Mesan AS
unfiltered og json
case req @ POST(Path("/person")) => { val x = Body.string(req) val p: Person = read[Person](x) val id: Int = personer.add(p)
Created ~> Json("id" -> id)}
case class Person(name: String, age: Int)
© Mesan AS
json
val json = """{"name": "Petter", "age": 22}"""read[Person](json)
val json = """{"name":"Petter"}"""read[Person](json)
=> Person(Petter, 22)
=> org.json4s.package$MappingException: No usable value for age Did not find value which can be converted into int
© Mesan AS
Validering av input
def add(p: Person): Int = { Validate.isTrue(p.age > 18 && p.age < 60) 1}
case req @ POST(Path("/person")) => { val x = Body.string(req) val p: Person = read[Person](x) val id: Int = personer.add(p)
Created ~> Json("id" -> id)}
© Mesan AS
Fra en klients perspektiv...
© Mesan AS
Fra en utviklers perspektiv
Dette føles ikke bra!
© Mesan AS
validering med scalaz
case req @ POST(Path("/personz")) => { val x = Body.string(req) val p = fromJSON[Person](parse(x)) val id = p map personer.add
id match { case Success(i) => Created ~> Json("id"->i) case Failure(errors) => Forbidden ~> Json("errors" -> jsonErrors(errors).toList) }}
© Mesan AS
validering med scalaz
case req @ POST(Path("/personz")) => { val x = Body.string(req) val p = fromJSON[Person](parse(x)) val id = p map personer.add
id match { case Success(i) => Created ~> Json("id"->i) case Failure(errors) => Forbidden ~> Json("errors" -> jsonErrors(errors).toList) }}
implicit val personR: JSONR[Person] = Person.applyJSON(field[String]("name"), validate[Int]("age") >==> min(18) >==> max(60))
© Mesan AS
Fra en klients perspektiv...
© Mesan AS
Fra en utviklers perspektiv
Mye bedre?! Men?!
>==> fromJSON[Person]
Kleisli[Result, JValue, A]
type EitherNel[+a] = NonEmptyList[Error] \/ a
© Mesan AS
scalaz
import scalaz._import Scalaz._
© Mesan AS
Hva er scalaz?•Scalaz is a Scala library for functional programming.
•It provides purely functional data structures to complement those from the Scala standard library. It defines a set of foundational type classes (e.g. Functor, Monad) and corresponding instances for a large number of data structures.
© Mesan AS
Hva er scalaz?1.Funksjonelle datastrukturer
2.Syntaks for utvidelse av standard klasser
3.Typeklasser og instanser for disse
© Mesan AS
Syntaks
utvidelser til standard klasserog til hvordan du skriver kode
© Mesan AS
Equal
1 == 1=> true
1 == 1.0=> true
val x: Int = 1val y: Person = new Person("Petter")
x == y warning: comparing values of types Int and Person using `==' will always yield false
=> false
y == x warning: Person and Int are unrelated: they will most likely never compare equal
=> false
1 == 1=> false
© Mesan AS
Typesikker Equal
1 === 1=> true
1 === 1.0 error: could not find implicit value for parameter F0: scalaz.Equal[Any]
1 =/= 1=> false
1 =/= 1.0 error: could not find implicit value for parameter F0: scalaz.Equal[Any]
1 === 2=> false
1 =/= 2=> true
© Mesan AS
Boolean
true && true=> true
true /\ true=> true
true && false=> false
true /\ false=> false
(if (true) "a" else "b")=> "a"
true ? "a" | "b"=> "a"
true option "a"=> Some("a")
false option "a"=> (None:Option[String])
© Mesan AS
Option
Some(42)=> Some[Int] = Some(42)
None=> None.type = None
some(42)=> Option[Int] = Some(42)
none[Int]=> Option[Int] = None
42.some=> Some(42)
val o = 42.someo getOrElse 1o | 1=> 42
val n = option.none[Int]n getOrElse 1n | 1=> 1
~o=> 42
~n=> 0
© Mesan AS
Index
val l = List(1,2,3,4)
l(0)=> 1l(2)=> 3
l(-1)=> IndexOutOfBoundsExceptionl(4)=> IndexOutOfBoundsException
l index 0=> Some(1)l index 2=> Some(3)
l index -1=> Nonel index 4=> None
© Mesan AS
Alle instanser - Id
"a" ?? "b"=> "a"
val x: String = nullx ?? "asdf"=> "asdf"
def f(i:Int) = i + 1(1 |> f)=> 2
1 + 2 + 3 |> {_ * 6}=> 36
© Mesan AS
Typeklasser
© Mesan AS
Hva er en Typeklasse?•En form for et interface som definerer oppførsel til en type
•oppførselen er definert utenfor typen
•alle typer som er medlem i typeklassen har implementasjoner av oppførselen
•ikke et interface som i Java
© Mesan AS
Typeklasser i Scala
trait Truthy[A] { def truthy(a: A): Boolean}
implicit val intTruthy: Truthy[Int] = new Truthy[Int] { def truthy(a: Int): Boolean = a match { case 0 => false case _ => true }}
intTruthy.truthy(1)=> trueintTruthy.truthy(0)=> false
© Mesan AS
Typeklasser i Scala
object Truthy { def apply[A](implicit A: Truthy[A]): Truthy[A] = A}
> Truthy[Int].truthy(0)=> falseTruthy[Int].truthy(1)=> true
1.truthy=> true
© Mesan AS
Typeklasser i Scala
trait TruthyOps[A] { def self: A implicit def F:Truthy[A] def truthy: Boolean = F.truthy(self)}
object ToTruthyOps { implicit def toTOps[A](a: A)(implicit ev: Truthy[A]) = new TruthyOps[A] { def self: A = a implicit def F = ev }}
1.truthy=> true
© Mesan AS
Typeklasser i Scala
def iffy[A: Truthy, B](t: A)(ifT: =>B)(ifF: =>B): B = { if (t.truthy) ifT else ifF}
iffy(1)("Ja!")("Neeei!")=> Ja!
iffy(0)("Ja!")("Neeei!")=> Neeei!
© Mesan AS
Typeklasser i Scalaz
© Mesan AS
Typeklasser i Scalaz
© Mesan AS
Equaltrait Equal[F] { self => def equal(a1: F, a2: F): Boolean}
implicit val intInstance: Monoid[Int] with Enum[Int] with Show[Int] = new Monoid[Int] with Enum[Int] with Show[Int] {....}
trait EqualOps[F] extends Ops[F] { final def ===(other: F): Boolean = ??? final def =/=(other: F): Boolean = ???}
1 === 1=> true
© Mesan AS
Equal
case class Car(val model: String)implicit val carEqual = new Equal[Car] { def equal(a1: Car, a2: Car): Boolean = a1.model == a2.model}
Car("Honda") === Car("Toyota")=> false
Car("Honda") === Car("Honda")=> true
© Mesan AS
SemiGroup
trait Semigroup[F] { self => def append(f1: F, f2: => F): F}
trait SemigroupOps[F] extends Ops[F] { implicit def F: Semigroup[F]
final def |+|(other: => F): F = ??? final def mappend(other: => F): F = ???}
1 |+| 2=> 3
List(1,2) |+| List(3,4)=> List(1,2,3,4)
(1,2) |+| (3,4)=> (4,6)
© Mesan AS
Monoid
trait Monoid[F] extends Semigroup[F] { self => def zero: F}
trait MonoidOps[F] extends Ops[F] { final def multiply(n: Int): F = ??? final def ifEmpty[A](tv: => A)(fv: => A) = ???}
(0.ifEmpty("Empty")("NonEmpty"))=> "Empty"(1.ifEmpty("Empty")("NonEmpty"))=> "NonEmpty"
"a" multiply 3=> "aaa"~o.none[Int]=> 0~3.some=> 3
© Mesan AS
Monoid avler Monoid!•Option[A] er Monoid hvis A er Monoid
•Map[K, A] er Monoid hivs A er Monoid
•(A, B) er Monoid hvis A og B er Monoid!
(1,2) |+| (3,4)=> (4,6)
Map(1->1, 2->3) |+| Map(1->4, 3->6)=> Map(1->5, 2->3, 3->6)
(1.some, 2.some) |+| (3.some, o.none[Int])=> (4.some, 2.some)
© Mesan AS
Og Monider er overalt..•Tall (Int, Double, BigDecimal....)
•Penger (Din egen klasse)
•Dato / Perioder
•Kun fantasien setter grenser...
100.NOK |+| 250.NOK=> Money(350, NOK)
1.day |+| 1.week=> Period(1, 1)
© Mesan AS
Functor
trait Functor[F[_]] { self => def map[A, B](fa: F[A])(f: A => B): F[B]}
List(1, 2, 3) map {_ + 1}=> List(2,3,4)
trait FunctorOps[F[_],A] extends Ops[F[A]] { final def map[B](f: A => B): F[B] = ??? final def fpair: F[(A, A)] = ??? final def fproduct[B](f: A => B): F[(A, B)] = ??? final def >|[B](b: => B): F[B] = ???}
List(1,2,3) >| "a"=> List("a","a","a")
(List(1,2,3) fpair)=> List((1,1),(2,2),(3,3))
List(1,2,3) fproduct {_*2}=> List((1,2), (2,4), (3,6))
© Mesan AS
Applicative
trait Applicative[F[_]] extends Apply[F] { self => def point[A](a: => A): F[A]}
trait Apply[F[_]] extends Functor[F] { self => def ap[A,B](fa: => F[A])(f: => F[A => B]): F[B]}
trait ApplyOps[F[_],A] extends Ops[F[A]] { final def <*>[B](f: F[A => B]): F[B] = ??? final def |@|[B](fb: F[B]):ApplicativeBuilder = ???}
© Mesan AS
Applicative
val f = (_:Int) * 32.some <*> f.some=> 6.some
9.some <*> { (_: Int) + 3 }.some=> 12.some
^(1.some, 2.some) {_ + _}=> 3.some
(1.some |@| 2.some) {_ + _}=> 3.some
© Mesan AS
Applicative
val f: (Int, Int) => Int = (x:Int, y:Int) => x + y
f(1,2)=> 3
f(1.some, 2.some)=> 3.some
f(1.some, o.none[Int])=> o.none[Int]
© Mesan AS
Monadtrait BindOps[F[_],A] extends Ops[F[A]] { def flatMap[B](f: A => F[B]) = ??? def >>=[B](f: A => F[B]) = ??? def >>[B](b: => F[B]): F[B] = ???}
val f: Int => Option[Int] = (x:Int) => (x * 10).some(9.some >>= f)
(9.some flatMap f)
(for { x <- 9.some y <- f(x)} yield y)=> 90.some
(9.some >> 4.some)=> 4.some
© Mesan AS
Validation
endelig
© Mesan AS
Validation
sealed trait Validation[+E, +A]
case class Success[E, A](a: A)extends Validation[E, A]
case class Failure[E, A](e: E)extends Validation[E, A]
type ValidationNel[+E, +X] =Validation[NonEmptyList[E], X]
© Mesan AS
Validation
1.success[String]=> scalaz.Validation[String,Int] = Success(1)
1.successNel[String]=> scalaz.ValidationNel[String,Int] = Success(1)
1.successNel=> scalaz.ValidationNel[Nothing,Int] = Success(1)
© Mesan AS
Validation
"Too young!".failNel[Int]=> Failure(NonEmptyList("Too young!"))
"Too young!".fail[Int]=> Failure("Too young!")
"Too young!".fail=> Failure("Too young!")
© Mesan AS
Validationcase class User(username:String)
def addUser(username: String): ValidationNel[String, User] = { findUser(username) match { case Some(_) => s"Username '${username}' taken!".failNel case _ => User(username).success }}
def findUser(username: String): Option[User] = { (username === "ovstetun") ? User("ovstetun").some | none}
addUser("per")=> Success(User("per"))
addUser("ovstetun")=> Failure(NonEmptyList("Username 'ovstetun' taken!"))
© Mesan AS
Validation
val e: Equal[Validation[String, Int]] = Equal[Validation[String, Int]]
val sg: Semigroup[Validation[String, Int]] = Semigroup[Validation[String, Int]]
val m: Monoid[Validation[String, Int]] = Monoid[Validation[String, Int]]
val a: Applicative[({type l[a] = Validation[String, a]})#l] = Validation.ValidationApplicative[String]
val t: Traverse[({type l[a] = Validation[String, a]})#l] = Traverse[({type l[a] = Validation[String, a]})#l]
© Mesan AS
Validation
case req @ POST(Path("/personz")) => { val x = Body.string(req) val p = fromJSON[Person](parse(x)) val id = p map personer.add
id match { case Success(i) => Created ~> Json("id"->i) case Failure(errors) => Forbidden ~> Json("errors" -> jsonErrors(errors).toList) }}
def add(p: Person): Int = ???
© Mesan AS
Konklusjoner
© Mesan AS
Konklusjoner•Scalaz er ikke farlig
•Gir en felles måte å løse like problemer
•Man trenger ikke skjønne hvordan et konsept er implementert for å bruke det
•Validation er killer feature for meg
•Gjenbrukbar, komponerbar og vedlikeholdbar kode
© Mesan AS
Referanser•Scalaz på github
•http://eed3si9n.com/learning-scalaz/
© Mesan AS
Q & A