Functional patterns for Scala practionners

I'm online!



λ



Scala



JS

zygoHistoPrepro 
  :: (Unfoldable t, Foldable t) 
  => (Base t b -> b) 
  -> (forall c. Base t c -> Base t c) 
  -> (Base t (EnvT b (Stream (Base t)) a) -> a) 
  -> t
  -> a
zygoHistoPrepro f g t =
  gprepro (distZygoT f distHisto) g t
-- unless you want
-- a generalized zygomorphism.

I won't talk about high level abstractions but bases used to build upon

No swagoid yolomorphisms today

Tools they're built upon

Just examples, YMMV. Read code, find your own style. You can go a lot farther than what I'll show, use more generic coding style. bête et méchant

Object / FP

OO has plenty of beginner-oriented litterature. FP patterns in scala is a bit less documented. It's often ported from haskell / other fp-centric languages without ceremony.

Object / FP

I'll focus on patterns common in the FP world (not intrinsically FP, though). Object-orientation will play a part, but more as implementation details than design

What is FP?

Many definitions

Programming with values

The control flow is data dependency

Expressions

Expressions

val x = if(yolo) {
  swag()
} else {
  moreRegularOutfit()
}

Expressions

val x = foo match {
    case Yolo(swag) => yoloSwag(swag)
    case NotYolo => notYoloSwag()
}

Typed FP

Since all the flow is data, you can get the type system to check your control flow. Which is a good thing

Algebraic design

How do we model data?

Algebraic data types

Sum / Product

Product

Tuples, case classes Cartesian product

Construction

val c = ("Clément", 26)

case class User(name: String, age: Int)
val cc = User("Clément", 26)

Elimination

val (name, age) = ("Clément", 26)

val User(name, age) = User("Clément", 26)

Product

// 2 * 2 = 4
(true, true)
(true, false)
(false, true)
(false, false)

Sum

Disjointed union Closed

In Haskell

data JsonElem = JsonNull
              | JsonBoolean Bool
              | JsonString String
              | JsonNumber Double
              | JsonArray [JsonElem]
              | JsonObject (Map String JsonElem)

In Scala?

No direct syntactic support for disjoint unions, sealed trait

sealed trait JsonElem

case object JsonNull
  extends JsonElem
case class  JsonBoolean(v: Boolean)
  extends JsonElem
case class  JsonString(v: String)
  extends JsonElem
case class  JsonNumber(v: Double)
  extends JsonElem
case class  JsonArray(v: Seq[JsonElem])
  extends JsonElem
case class  JsonObject(v: Map[String, JsonElem])
  extends JsonElem
def stringify(json: JsonValue) =
  jsonValue match {
    case JsonNull => "null"
    case JsonBoolean(v) => v.toStringcase JsonArray(v) =>
      v.map(stringify(_))
       .mkString("[", ",", "]")
  }

<console>:9: warning: match may not be exhaustive.
It would fail on the following input: JsonString
       def stringify(json: JsonValue) = json match {
                                        ^
stringify: (json: JsonValue)String

Warning for non-exhaustive patterns

Scala niceties

case class
  JsonArray(v: Seq[JsonElem])
  extends JsonElem with JsonValue

case class
  JsonObject(v: Map[String, JsonElem])
  extends JsonElem with JsonValue

Since it's not directly encoded in the language, we have more flexibility. Use with caution

OOP: methods on classes

ADTs: pattern matching

OOP: easy to add cases

FP: easy to add functions

Hand rolled ADT or generic types?

Use your best judgment. Concision / type safety

("Clément", 26)

User(name = "Clément", age = 26)
val v: Either[String, Int] =
  Left("error")

sealed trait MyEither

case class MyLeft(v: String)
  extends MyEither

case class MyRight(v: Int)
  extends MyEither

Sum / Product

+ / *

Fun equivalences

a*1 <=> a
a+0 <=> a

Fun equivalences

(a*b)*c <=> a*(b*c) <=> a*b*c
(a+b)+c <=> a+(b+c) <=> a+b+c

a*(b+c) <=> (a*b)+(a*c)
x+x+…+x <=> n*x

Fun equivalences

c^(a+b) <=> c^a * c^b

(): Unit

void: Void

a*1 <=> a

(A,Unit) <=> A

a+0 <=> a

A | Void <=> A

(a*b)*c <=>
a*(b*c)

a*b*c

(("Clément", 26), "Éol")

("Clément", (26, "Éol))

("Clément", 26, "Éol")

Flatten tuples, case classes

(User("Clément", 26), Pet("Éol"))


UserWithPet("Clément", 26, "Éol")

Flatten tuples, case classes

(a+b)+c <=>
a+(b+c)

a+b+c

c match {
  case Left(a)         => "Left " + a
  case Right(Left(a))  => "Middle " + a
  case Right(Right(a)) => "Right " + a
}

Flatten unions

c match {
  case Left(a)   => "Left " + a
  case Middle(a) => "Middle " + a
  case Right(a)  => "Right " + a
}

Flatten unions

a*(b+c) <=> (a*b)+(a*c)

("X", Left("Y"))
("X", Right(42))

Left(("X", "Y"))
Right(("X", 42))

Factor out common properties

x+x+…+x <=> n*x

sealed trait X
case class Bad(v: String) extends X
case class Good(v: String) extends X

case class Y(v: String, isGood: Bool)

Especially, factor out common properties and use an enum… or do the converse

c^(a+b) <=> c^a * c^b

Case analysis

Case analysis: fold

By the way

Avoid booleans

Programming with

values

Programming with contextualized values

Error handling

Option

Call it maybe

Only one thing can go wrong

Either

Right contains the expected value

Left contains the error

Use an ADT to describe the error

Either[String, A]

sealed trait InputError

case class MissingUsername
  extends InputError

case class InvalidEmail
  extends InputError
val a: Either[InputError, User] = ???

a match {
  case Right(user) => ???
  case Left(MissingUsername) => ???
  case Left(InvalidEmail) => ???
}

Either: not biased

for {
  a <- Right("operation 1 ok").right
  b <- Left("operation 2 failed").right
  c <- Left("operation 3 failed").right
} yield c

Either: not biased

res8: scala.util.Either[String,String] =
  Left(operation 2 failed)

Inference issues

scala> Option("test").fold(Left("error"))(Right.apply)
<console>:10: error: polymorphic expression
                     cannot be instantiated to expected type;
 found   : [A, B](b: B)scala.util.Right[A,B]
 required: String => scala.util.Left[String,Nothing]
              Option("test").fold(Left("error"))(Right.apply)

Quirky

scala> Option("test").toRight("error")
res1:
  Product
  with Serializable
  with scala.util.Either[String,String] =
    Right(test)

Consider using cats.Xor

cats.Xor

for {
  a <- "ok".right
  b <- "error 1".left
  c <- "error 2".left
} yield c

Right-biased, more explicit, less subtyping issues

cats.Xor

res16: cats.Xor[String,String] =
    Left(error 1)

scala 2.12 yay \o/

for {
  a <- Left("error")
  b <- Right(a)
} yield b

scala 2.12 yay \o/

scala> Option("test").toRight("error")
res10: scala.util.Either[String,String] = Right(test)

Xor will be retired

Either and Xor fail fast

Error accumulation

Consider using cats.Validated

cats.Validated

def validateEmail(value: String):
  ValidatedNel[String, String] = {

    value.validNel
    // or
    "error".invalidNel
}

NonEmptyList

cats.Validated

val user = (
  validateEmail(email) |@|
  validateUsername(username)) {
  case (e, u) =>

    User(e, u)
}

scalaz.Validation

    Valid(User(email, username))

scalaz.Validation

    Invalid(
      NonEmptyList(
        "invalid username"))

cats.Validated

    Valid(
      NonEmptyList(
        "invalid email",
        "invalid username"))

Don't always flatten your errors

Extensibility

Ad-Hoc polymorphism

But a little less Ad-Hoc

Monoid example

Big Data™

The only thing you need to know to become a big data expert

Combine 2 values

(associatively)

Neutral element

(left & right identity)

First try

trait Monoid {
  def combine(o: Monoid): Monoid
}

First try

class MyClass extends Monoid {
  def combine(o: Monoid) = ???
}

Information Loss

Can't add behaviour to final classes

How to encode zero?

External declaration

External declaration

Declare behaviour

trait Monoid[A] {
  def mzero: A
  def mappend(a: A, b: A): A
}

No problem with final types

No information loss

Implement it

val stringMonoid = new Monoid[String] {
  def mzero = ""
  def mappend(a: String, b: String) =
    a + b
}

Use it

def mconcat[A]
  (elems: Seq[A])
  (ev: Monoid[A]) = {

  elems.foldLeft(ev.mzero)(ev.mappend)

}
mconcat(Seq("1", "2", "3"))(stringMonoid)
// "123"

mconcat(Seq(1, 2, 3, 4))(addIntMonoid)
// 10

mconcat(Seq(1, 2, 3, 4))(multIntMonoid)
// 24

Automatic wiring

Implement it

implicit val stringMonoid =
new Monoid[String] {
  def mzero = ""
  def mappend(a: String, b: String) =
    a + b
}

Use it

def mconcat[A]
  (elems: Seq[A])
  (implicit ev: Monoid[A]) = {

  elems.foldLeft(ev.mzero)(ev.mappend)

}

Use it

def mconcat[A: Monoid](elems: Seq[A]) = {
  val ev = implicitly[Monoid[A]]
  elems.foldLeft(ev.mzero)(ev.mappend)
}

Use it

mconcat(Seq("1", "2", "3"))
// "123"

mconcat(Seq(1, 2, 3))
// ???

Typeclass convergence

Lawless Typeclasses

import simulacrum._

@typeclass trait Semigroup[A] {
  @op("|+|") def append(x: A, y: A): A
}

Serialization

trait ToJson[A] {
  def toJson(v: A): JsonElem
}
implicit def mapToJson[A: ToJson]() =
  new ToJson[Map[String, A]] {


  val ev = implicitly[ToJson[A]]

  def toJson(vs: Map[String, A]) =
  JsonObject(
    vs.mapValues(ev.toJson _)
  )
}

Central to Play!'s design

JSON

DB objects

QS parameters

Forms

Read cats code

Property-Based tests

« there exists »

tests show the presence of bugs, not their absence

« for all »

Scalacheck

property("substring") =
  forAll { (
     a: String,
     b: String,
     c: String) =>
    (a+b+c)
     .substring(
      a.length,
      a.length+b.length) == b
  }

Separate effects from logic

Separate decision from interpretation Keep a (mostly) pure core, push effects to the boundaries Effects described as data structures can be test, acted upon, batched, sometimes reversed.

Encode effects description as an ADT

case class AddUser(user: User)

case class TransferAmount(
  to: User,
  from: User,
  amount: Money)

Declare actions / effects

Interpret them "at the end of the world"

Testability

Test the output of the business logic code without having to actually exectute the effects

Flexibility

Batch, deduplicate, compress. Persist the effects description directly to be able to go back in time. Event sourcing's good, m'kay?

Go back in time

Recap

ADTs

Materialized errors

Typeclasses

Property-based testing

Segregated effects

Read FP in Scala

Thanks

Thanks

http://cltdl.fr/gifs

Give Clever Cloud a try!

devoxxroxx2016

I'm online!