en nybegynners introduksjon til scalaz

Post on 21-May-2015

318 Views

Category:

Technology

1 Downloads

Preview:

Click to see full reader

DESCRIPTION

My presentation at Javazone 2013 http://jz13.java.no/presentation.html?id=6c09d5d7

TRANSCRIPT

© 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

© Mesan AS

Takk for meg!

tmo@mesan.no@ovstetun

github.com/ovstetun/beginner-scalaz

top related