Introduces the functional programming ideas of Functor, Apply, Applicative And Monad. Shows how to implement each in Scala with Scalaz and how to validate the implementation using property based test using specs2 and scalacheck.
7. IF A PIZZA COSTS $16 AND YOU BUY
TWO HOW MUCH DO YOU SPEND?
8. A MARIO KART COSTS $20 PER DAY
TO RENT. HOW MUCH DOES IT COST
TO RENT FOR TWO DAYS?
9. DON'T JUST THINK ABOUT THE
MATHS
Think about the process you are going through before you
get to the maths.
What information are you keeping and what information are
you dropping?
11. ABSTRACTION
Abstraction is an emphasis on the idea, qualities and
properties rather than the particulars
The importance of abstraction is derived from its ability to
hide irrelevant details
It doesn't matter if its pizza, mario carts or anything else we
take the cost and muliply it by some factor.
12. WHAT IS THE REALATION SHIP
BETWEEN A POLOGON:
A 3 sided triangle
A 4 sided quadrilateral
13. WHAT HAS AM × AN = AM+N GOT IN
COMMON WITH:
a2 × a3 = (a × a) × (a × a × a) = a5
a3 × a4 = (a × a × a) × (a × a × a × a) = a7
19. IT ALL DEPENDS ON
CONTEXT
A mile is along way if you are an Ant but not if you are
flying in an aeroplane.
A year is a long time for a Mayfly that lives for only 5
minutes. But in geological time it's insignificant.
28. HOW TO DEFEAT A GOOMBA
Stomp it and it turns into a coin
case class Coin()
case class Goomba()
def stomp(g:Goomba) = Coin()
The function stomp is our pipe, that transforms from a
Goomba to a Coin.
29. LUIGIS VACUUM TO COLLECT
GOOMBAS
class Vacuum {
def collect(g:Goomba) = stomp(s)
}
val vacuum = new Vacuum()
val goomba = new Goomba()
vacuum.collect(goomba)
//Coin()
30. WHAT HAPPENS WHEN THE
GOOMBA ESCAPES THE SUCTION?
val vacuum = new Vacuum()
vacuum.collect(null)
32. CHECK FOR NULLS
class Vacuum {
def collect(s:Goomba) = if (s == null) null else stomp(s)
}
But then the calling class runs the risk of NullPointer
exceptions.
35. sealed trait Cage[T]
case class FullCage[T](value: T) extends Cage[T]
case class EmptyCage[T]() extends Cage[T]
object Cage {
def apply[T](x: T):Cage[T] =
if (x == null) EmptyCage[T]() else FullCage(x)
}
class Vacuum {
def collect(c:Cage[Goomba]):Cage[Coin] = c match {
case EmptyCage() => EmptyCage[Coin]()
case FullCage(s) => FullCage(stomp(s))
}
}
val vac = new Vacuum()
vac.collect(Cage(Goomba()))
//FullCage[Coin](Coin())
vac.collect(Cage(null))
//EmptyCage[Coin]()
38. WHY LIMIT OURSELF TO JUST
STOMPING GOOMBAS IN THE CAGE?
class Vacuum {
def collect[A,B](c:Cage[A], f:A => B):Cage[B] = c match {
case EmptyCage() => EmptyCage[B]()
case FullCage(s) => FullCage(f(s))
}
}
39. Turn it into a trait
trait Collector {
def collect[A,B](c:Cage[A], f:A => B):Cage[B]
}
class Vacuum extends Collector {
def collect[A,B](c:Cage[A], f:A => B):Cage[B] = c match {
case EmptyCage() => EmptyCage[B]()
case FullCage(s) => FullCage(f(s))
}
}
40. Parameterise the trait
trait Collector[F[_]] {
def collect[A,B](c:F[A], f:A => B): F[B]
}
object Vacuum extends Collector[Cage] {
def collect[A,B](c:Cage[A], f:A => B):Cage[B] = c match {
case EmptyCage() => EmptyCage[B]()
case FullCage(s) => FullCage(f(s))
}
}
43. THE FUNCTOR
DEFINITION IN SCALAZ
package scalaz
trait Functor[F[_]] extends InvariantFunctor[F] { self =>
...
/** Lift `f` into `F` and apply to `F[A]`. */
def map[A, B](fa: F[A])(f: A => B): F[B]
...
}
44. HOW DO YOU USE IT?
object CageFunctor extends Functor[Cage] {
def map[A,B](c:Cage[A])(f:A => B):Cage[B] = c match {
case EmptyCage() => EmptyCage[B]()
case FullCage(s) => FullCage(f(s))
}
}
CageFunctor.map(Cage(Gummba()))(stomp)
49. HOW?
3 COMPONENTS
1. The type class
2. Instances for particular types
3. Interface methods for the api
50. THE TYPE CLASS
Provide a generic type of what we want to implement.
trait ProtoBuffWriter[A] {
def write(value: A): Array[Byte]
}
51. TYPE CLASS INSTANCES
Provide implementations for the types we care about.
Create concrete implementations of the type class and mark
them as implicit.
object DefaultProtoBuffWriters {
implicit val coinWriter = ProtoBuffWriter[Coin] { .... }
implicit val goombaWriter = ProtoBuffWriter[Goomba] { .... }
// etc ...
}
52. INTERFACES
What is exposed to clients.
Generic methods that accept instances of the type class as
implicit params.
TWO WAYS OF DOING IT
53. INTERFACE OBJECTS
All methods in a singleton.
object ProtoBuff {
def toProtoBuff[A](value: A)
(implicit writer: ProtoBuffWriter[A]): Array[Byte] {
writer.write(value)
}
}
import DefaultProtoBuffWriters._
val protoBuff: Array[Byte] = ProtoBuff.toProtoBuff(Coin())
54. INTERFACE SYNTAX
Pimp existing types with interface methods.
object ProtoBuffSyntax {
implicit class ProtoBuffWriter[A](value: A) {
def toProtoBuff(implicit writer: ProtoBuffWriter[A])
: Array[Byte] = {
writer.write(value)
}
}
}
import DefaultProtoBuffWriters._
import ProtBuffSyntax._
val protoBuff: Array[Byte] = Coin().toProtoBuff
55. WHAT ABOUT SCALAZ?
Pimp existing types
Uses the Type classes in Ops classes
Ops classes use the Type class and provide more methods
import scala.language.implicitConversions
sealed trait ToProtoBuffWriterOps {
implicit def ToProtoBuffWriterOps[A](v: A)
(implicit F: ProtoBuffWriter[A]) = new ProtoBuffWriterOps(v)
}
object protoBuffWriter extends ToProtoBuffWriterOps
import sun.misc.BASE64Encoder //for example only
class ProtoBuffWriterOps[A](val self: A)
(implicit val F: ProtoBuffWriter[A]) {
def write(value: A) = F.write(value)
def writeBase64(value: A) =
new BASE64Encoder().encodeBuffer(write(value))
}
57. SCALAZ FUNCTOR SYNTAX:
TOFUNCTOROPS
scalaz.syntax.FunctorSyntax.scala
trait ToFunctorOps extends ToFunctorOps0 with ToInvariantFunctorOps
{
implicit def ToFunctorOps[F[_],A](v: F[A])(implicit F0: Functor[F])
= new FunctorOps[F,A](v)
...
}
Given a F[A] and a implicit Functor[F] in scope add all the
FunctorOps to F[A]
58. SCALAZ FUNCTOR SYNTAX:
FUNCTOROPS
scalaz.syntax.FunctorSyntax.scala
final class FunctorOps[F[_],A] private[syntax]
(val self: F[A])(implicit val F: Functor[F]) extends Ops[F[A]] {
...
final def map[B](f: A => B): F[B] = F.map(self)(f)
...
}
Given a F[A] and a implicit Functor[F] in scope delegate the
map method to the Functor[F] in scope
60. CAGE FUNCTOR AGAIN
scalaz.syntax.FunctorSyntax.scala
import scalaz.Functor
implicit object CageFunctor extends Functor[Cage] {
def map[A,B](c:Cage[A])(f:A => B):Cage[B] = c match {
case EmptyCage() => EmptyCage[B]()
case FullCage(s) => FullCage(f(s))
}
}
import scalaz.syntax.functor
Cage(Goomba()).map(stomp)
61. THE FUNCTOR LAWS
Mapping preserves identity
If we map the id function over a functor, the
functor that we get back should be the
same as the original functor
Mapping respects composition
Composing two functions and then
mapping the resulting function over a
functor should be the same as first
mapping one function over the functor and
then mapping the other one
62. DOES YOUR FUNCTOR BREAK LAWS?
import org.scalacheck.Arbitrary
import org.specs2.scalaz.Spec
import scalaz.Equal
import scalaz.scalacheck.ScalazProperties
class CageFunctorSpec extends Spec {
implicit val abrCage = Arbitrary[Cage[Int]] {
for {
ns <- Arbitrary.arbInt.arbitrary
} yield Cage(ns)
}
implicit val cageEqual = Equal.equal[Cage[Int]]((a, b) => a == b)
checkAll(ScalazProperties.functor.laws[Cage])
}
val scalazVersion = "7.1.3"
libraryDependencies ++= Seq(
"org.scalaz" %% "scalaz-core" % scalazVersion,
"org.specs2" %% "specs2-core" % "2.4" % "test",
"org.typelevel" %% "scalaz-specs2" % "0.3.0" % "test"
)
63. REMEMBER THE THREE POINTS?
Abstraction: Functor is a abstract concept
Generalization: There is a set of objects that can be
mapped over
What about the context?
67. SCALA.OPTION
sealed abstract class Option[+A] extends Product with Serializable {
...
final def map[B](f: A => B): Option[B] =
if (isEmpty) None else Some(f(this.get))
...
}
Scalaz provides implicits to convert Option to a Functor trait
import scalaz.std.option._
import scalaz.std.anyVal._
checkAll(ScalazProperties.functor.laws[Option])
Option is context for a computation that might fail
68. LIST ARE ALSO FUNCTORS
sealed abstract class List[+A] { ....
final def map[B](f: (A) ⇒ B): List[B]
...
}
Scalaz provides implicits to convert List to a Functor trait
import scalaz.std.list._
import scalaz.std.anyVal._
import org.specs2.scalaz.Spec
import scalaz.scalacheck.ScalazProperties
class ListFunctorSpec extends Spec {
checkAll(ScalazProperties.functor.laws[List])
}
If 6 is deterministic and having one value.
The List context such as List(1,10,3,4) can be thought of as
having multipule values at once.
Or no values if empty
69. DISJUNCTIONS ARE FUNCTORS
import org.specs2.scalaz.Spec
import scalaz.scalacheck.ScalazProperties
class ListFunctorSpec extends Spec {
implicit val abrStringIntEither = Arbitrary[/[String, Int]] {
for {
ns <- Arbitrary.arbInt.arbitrary
} yield /-(ns)
}
implicit val disjuncEqual =
Equal.equal[/[String, Int]]((a, b) => { (a,b) match {
case(-/(l1), -/(l2)) => l1 == l2
case(/-(r1), /-(r2)) => r1 == r2
case _ => false
}
})
//left type param is fixed
checkAll(ScalazProperties.functor.laws[({type λ[α] = /[String, α]})#λ])
}
72. FUNCTIONS IN A CONTEXT HAVE
USEFUL PROPERTIES
Functors let us write ordinary functions
Then promote those functions into every context that might
need that code
As new contexts arise we just define new functors to
promote our ordinary code to work in those contexts.
73. ///Ordinary function
def stomp(g:Goomba) = g.stomp()
///The context
sealed trait Cage[T]
case class FullCage[T](value: T) extends Cage[T]
case class EmptyCage[T]() extends Cage[T]
object Cage {
def apply[T](x: T):Cage[T] =
if (x == null) EmptyCage[T]() else FullCage(x)
}
///Promote into context
import scalaz.Functor
implicit object CageFunctor extends Functor[Cage] {
def map[A,B](c:Cage[A])(f:A => B):Cage[B] = c match {
case EmptyCage() => EmptyCage[B]()
case FullCage(s) => FullCage(f(s))
}
}
///use
import scalaz.syntax.functor
Cage(Goomba()).map(stomp)
77. PIRANHA PLANT
case class Coin()
case class Fireball()
case class PiranhaPlant()
def shoot(plant:PiranhaPlant, fireball:Fireball): Coin = Coin()
78. THE PLANT IS GENERATED FROM A
UNRELIABLE SOURCE
Wrap the plant in a Cage and map over it?
Cage(PiranhaPlant()).map(shoot _)
<console>:31: error: type mismatch;
found : (PiranhaPlant, Fireball) => Coin
required: PiranhaPlant => ?
Cage(PiranhaPlant()).map(shoot _)
</console>
80. CURRING IS PARTIAL APPLICATION
Translating the evaluation of a function that takes multiple
arguments into evaluating a sequence of functions, each
with a single argument
(shoot _).curried
//res41: PiranhaPlant => (Fireball => Coin) = <function1>
</function1>
81. MAP THE CURRIED SHOOT FUNCTION
Cage(PiranhaPlant()) map {shoot _}.curried
//Cage[Fireball => Coin] = ...
82. WHAT IF THE FIREBALL PARAMETER
GENERATION IS IN A CONTEXT?
Functor only support mapping functions over functor
def map[A, B](fa: F[A])(f: A => B): F[B]
We need to map function in a functor over a value in a
functor
84. WHAT WOULD OUR VACUUM LOOK LIKE?
implicit object CageApply extends Apply[Cage]{
override def ap[A, B](fa: => Cage[A])
(fab: => Cage[(A) => B]): Cage[B] = fab match {
case FullCage(f) => fa match {
case FullCage(x) => FullCage(f(x))
case EmptyCage() => EmptyCage[B]()
}
case EmptyCage() => EmptyCage[B]()
}
override def map[A, B](fa: Cage[A])
(f: (A) => B): Cage[B] = CageFunctor.map(fa)(f)
}
85. HOW WOULD YOU USE IT?
val partialShoot = Cage(PiranhaPlant()) <*> Cage((shoot _).curried)
val optCoin = Cage(Fireball()) <*> partialShoot
//optCoin: Cage[Coin] = FullCage(Coin())
val optCoin = EmptyCage[Fireball]() <*> partialShoot
//optCoin: Cage[Coin] = EmptyCage[Coin]()
86. WHAT HAVE WE DONE?
Taken a function that takes two values.
Turned it into a function that takes two values in a context.
87. TESTING THE LAWS
import org.scalacheck.Arbitrary
import org.specs2.scalaz.Spec
import scalaz.Equal
import scalaz.scalacheck.ScalazProperties
class CageApplySpec extends Spec {
implicit val abrCage = Arbitrary[Cage[Int]] {
for {
ns <- Arbitrary.arbInt.arbitrary
} yield Cage(ns)
}
implicit val arbCageIntToInt = Arbitrary[Cage[Int => Int]] {
for{
multi <- Arbitrary.arbInt.arbitrary
} yield Cage((x:Int) => x * multi)
}
implicit val cageEqual = Equal.equal[Cage[Int]]((a, b) => a == b)
checkAll(ScalazProperties.apply.laws[Cage])
}
88. OPTION APPLY: OPTIONINSTANCES
package scalaz
package std
override def ap[A, B](fa: => Option[A])
(f: => Option[A => B]) = f match {
case Some(f) => fa match {
case Some(x) => Some(f(x))
case None => None
}
case None => None
}
89. SHOOT THAT PIRANHA PLANT
import scalaz.std.option._
val partialShoot =
Option(PiranhaPlant()) <*> Option((shoot _).curried)
val optCoin = Option(Fireball()) <*> partialShoot
//optCoin: Option[Coin] = Some(Coin())
91. LIST AS AN APPLY CONTEXT
val partial = List(PiranhaPlant(), PiranhaPlant(),
PiranhaPlant()) <*> List((shoot _).curried)
List(Fireball()) <*> partial
//res23: List[Coin] = List(Coin(), Coin(), Coin())
92. DISJUNCTION AS AN APPLY
/.right[String, Goomba](Goomba()) <*>
/.right[String, Goomba => Coin]((_:Goomba) => Coin())
//res: scalaz./[String,Coin] = /-(Coin())
93. Apply lets you take a function that takes values and turn it
into a function that takes values in a context.
Write the code once and reuse it in the context you need
94. REMEMBER THE THREE POINTS?
Abstraction: Apply is a abstract concept
Generalization: There is a set of objects that implement
the Apply trait
Context: How the funciton is used depends on the Apply
Specialization we are using
100. THE CODE
case class KoopaParatroopa()
case class KoopaTroopa()
case class Shell()
case class Coin()
case class Fireball()
def shootKP(fb: Fireball, kt:KoopaParatroopa) = KoopaTroopa()
def shootKT(fb: Fireball, kt:KoopaTroopa) = Shell()
def shootS(fb: Fireball, kt:Shell) = Coin()
val cagedKoopa = ^(Cage(Fireball()),
Cage(KoopaParatroopa()))(shootKP)
val cagedShell = ^(Cage(Fireball()), cagedKoopa)(shootKT)
val cagedCoin = ^(Cage(Fireball()), cagedShell)(shootS)
//cagedCoin: Cage[Coin] = FullCage(Coin())
101. APPLICATIVE
trait Applicative[F[_]] extends Apply[F] { self =>
////
def point[A](a: => A): F[A]
...
}
implicit object CageApplicative extends Applicative[Cage] {
override def ap[A, B](fa: => Cage[A])
(fab: => Cage[(A) => B]): Cage[B] = fab match {
case FullCage(f) => fa match {
case FullCage(x) => FullCage(f(x))
case EmptyCage() => EmptyCage[B]()
}
case EmptyCage() => EmptyCage[B]()
}
override def point[A](a: => A): Cage[A] = Cage(a)
}
We no longer need to define map
override def map[A, B](fa: F[A])(f: A => B): F[B] =
ap(fa)(point(f))
102. USING APPLICATIVE
import scalaz.syntax.applicative._
val cagedKoopa = ^(Fireball().point[Cage],
KoopaParatroopa().point[Cage])(shootKP)
val cagedShell = ^(Fireball().point[Cage], cagedKoopa)(shootKT)
val cagedCoin = ^(Fireball().point[Cage], cagedShell)(shootS)
//cagedCoin: Cage[Coin] = FullCage(Coin())
103. SAME CODE DIFFERENT CONTEXT
val cagedKoopa = ^(Fireball().point[List],
KoopaParatroopa().point[List])(shootKP)
val cagedShell = ^(Fireball().point[List], cagedKoopa)(shootKT)
val cagedCoin = ^(Fireball().point[List], cagedShell)(shootS)
//cagedCoin: List[Coin] = List(Coin())
104. REMEMBER
1. Abstraction: The Applicative
2. Generalisation: The Applicative trait
3. The context: Different behaviours for the same code
110. HOW ABOUT THIS?
def marioWins = hitMario _ andThen hitMario andThen
hitMario andThen hitBowser andThen
hitBowser andThen hitBowser
marioWins(Hits(0,0))
//hits = Hits(3,3)
marioWins(Hits(3,0))
//marioWins(Hits(6,3))
Mario should have died
111. FAILING THE COMPUTATION?
Hits => Cage[Hits]
def hitMario2(hits: Hits):Cage[Hits] = hits match {
case ko:Hits if ko.mario + 1 - ko.bowser > 2 => EmptyCage[Hits]()
case Hits(mario, bowser) => Cage(Hits(mario + 1, bowser))
}
def hitBowser2(hits: Hits):Cage[Hits] = hits match {
case ko:Hits if ko.mario + 1- ko.bowser > 2 => EmptyCage[Hits]()
case Hits(mario, bowser) => Cage(Hits(mario, bowser + 1))
}
What's the problem?
125. REFERENCES
Learn you a Haskell for Greater Good
Leaning Scalaz
Functional Programming in Scala
Bartosz Blog
http://learnyouahaskell.com/
http://eed3si9n.com/learning-scalaz/
https://www.manning.com/books/functional-
programming-in-scala
http://bartoszmilewski.com/2014/10/28/category-theory-
for-programmers-the-preface/