Utilizamos tu perfil de LinkedIn y tus datos de actividad para personalizar los anuncios y mostrarte publicidad más relevante. Puedes cambiar tus preferencias de publicidad en cualquier momento.
Próxima SlideShare
Cargando en…5
×

# Functor, Apply, Applicative And Monad

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.

• Full Name
Comment goes here.

Are you sure you want to Yes No
• Inicia sesión para ver los comentarios

### Functor, Apply, Applicative And Monad

1. 1. FUNCTORS, APPLY, APPLICATIVE AND MONADS
2. 2. THAT'S JUST DULL
3. 3. SO LET'S TRY INSTEAD ...
4. 4. .
5. 5. WHY SHOULD YOU CARE? How do Functors, Apply, Applicative and Monad help you achieve early and continuous delivery of software?
6. 6. 3 CONCEPTS
7. 7. IF A PIZZA COSTS \$16 AND YOU BUY TWO HOW MUCH DO YOU SPEND?
8. 8. A MARIO KART COSTS \$20 PER DAY TO RENT. HOW MUCH DOES IT COST TO RENT FOR TWO DAYS?
9. 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?
10. 10. .
11. 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. 12. WHAT IS THE REALATION SHIP BETWEEN A POLOGON: A 3 sided triangle A 4 sided quadrilateral
13. 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
14. 14. .
15. 15. GENERALIZATION Relationship that holds between all members of some set of objects
16. 16. IS A MILE A LONG WAY?
17. 17. IS A YEAR A LARGE AMOUNT OF TIME?
18. 18. .
19. 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.
20. 20. TRAINING LEVEL
21. 21. WHAT IS FUNCTIONAL PROGRAMMING? Construct our programs using only pure functions. Pure functions have no side effects.
22. 22. WHY IS A FUNCTION LIKE A PIPE? Some thing goes into one end and something else comes out the other end
23. 23. Simple pipes simple can be joined together to form complex systems?
24. 24. WHAT'S SO GOOD ABOUT NO SIDE EFFECTS? It makes it easier to reason about what's going on
25. 25. IT'S IMPORTANT THAT FUNCTIONS LIKE PIPES DON'T LEAK
26. 26. WORLD 1-1 Functor Land
27. 27. GOOMBA PROBLEM
28. 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. 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. 30. WHAT HAPPENS WHEN THE GOOMBA ESCAPES THE SUCTION? val vacuum = new Vacuum() vacuum.collect(null)
31. 31. .
32. 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.
33. 33. There must be a better way
34. 34. SOLUTION Put the Goomba in a Cage
35. 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]()
36. 36. .
37. 37. CAN WE GENERALIZE THE VACUUM CLASS?
38. 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. 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. 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)) } }
41. 41. .
42. 42. THE FUNCTOR A functor is basically for things that can be mapped over.
43. 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. 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)
45. 45. IS THERE A BETTER WAY?
46. 46. .
47. 47. TYPE CLASSES IN 60 SECONDS
48. 48. WHY? Extend existing classes Without inheritance Without altering original source Keeps concerns seperate
49. 49. HOW? 3 COMPONENTS 1. The type class 2. Instances for particular types 3. Interface methods for the api
50. 50. THE TYPE CLASS Provide a generic type of what we want to implement. trait ProtoBuffWriter[A] { def write(value: A): Array[Byte] }
51. 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. 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. 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. 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. 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)) }
56. 56. !!WARNING!! Implicits like warp pipes can be dangerous
57. 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. 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
59. 59. FINALLY scalaz.syntax package object extends Syntaxes trait Syntaxes { object functor extends ToFunctorOps } import scalaz.syntax.functor
60. 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. 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. 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. 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?
64. 64. CONTEXT Context is the environment the function is applied in.
65. 65. MEET SOME MORE CONTEXTS
66. 66. 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
67. 67. 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
68. 68. 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, α]})#λ]) }
69. 69. SO WHY IS THIS SO HANDY?
70. 70. ORDINARY FUNCTIONS ARE SIMPLER TO: read write use reason about
71. 71. 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.
72. 72. ///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)
73. 73. .
74. 74. WORLD 1-2 Apply Land
75. 75. PIRANHA PLANT
76. 76. PIRANHA PLANT case class Coin() case class Fireball() case class PiranhaPlant() def shoot(plant:PiranhaPlant, fireball:Fireball): Coin = Coin()
77. 77. 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>
78. 78. CURRING
79. 79. 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>
80. 80. MAP THE CURRIED SHOOT FUNCTION Cage(PiranhaPlant()) map {shoot _}.curried //Cage[Fireball => Coin] = ...
81. 81. 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
82. 82. APPLY package scalaz trait Apply[F[_]] extends Functor[F] { self => //// def ap[A,B](fa: => F[A])(f: => F[A => B]): F[B] ... } package scalaz package syntax final class ApplyOps[F[_],A] private[syntax](val self: F[A]) (implicit val F: Apply[F]) extends Ops[F[A]] { final def <*>[B](f: F[A => B]): F[B] = F.ap(self)(f) ... } trait ToApplyOps extends ToApplyOps0 with ToFunctorOps { implicit def ToApplyOps[F[_],A](v: F[A])(implicit F0: Apply[F]) = new ApplyOps[F,A](v) ... }
83. 83. 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) }
84. 84. 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]()
85. 85. WHAT HAVE WE DONE? Taken a function that takes two values. Turned it into a function that takes two values in a context.
86. 86. 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]) }
87. 87. 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 }
88. 88. 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())
89. 89. SOME NICER SYNTAX import scalaz.std.option._ import scalaz.syntax.apply._ ^(Option(PiranhaPlant()), Option(Fireball()))(shoot) //res69: Option[Coin] = Some(Coin()) import scalaz.Apply Apply[Option] .lift2(shoot)(Option(PiranhaPlant()), Option(Fireball())) //res70: Option[Coin] = Some(Coin())
90. 90. LIST AS AN APPLY CONTEXT val partial = List(PiranhaPlant(), PiranhaPlant(), PiranhaPlant()) <*> List((shoot _).curried) List(Fireball()) <*> partial //res23: List[Coin] = List(Coin(), Coin(), Coin())
91. 91. DISJUNCTION AS AN APPLY /.right[String, Goomba](Goomba()) <*> /.right[String, Goomba => Coin]((_:Goomba) => Coin()) //res: scalaz./[String,Coin] = /-(Coin())
92. 92. 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
93. 93. 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
94. 94. .
95. 95. WORLD 1-3 Applicative
96. 96. KOOPA PARATROOPA
97. 97. HAS TO BE: 1. Koopa Paratroopa shot to a Koopa Troopa 2. Koopa Troopa is shot to a Shell 3. Shell is shot to a Coin
98. 98. .
99. 99. 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())
100. 100. 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))
101. 101. 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())
102. 102. 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())
103. 103. REMEMBER 1. Abstraction: The Applicative 2. Generalisation: The Applicative trait 3. The context: Different behaviours for the same code
104. 104. .
106. 106. BOWSER
107. 107. THE RULES Mario can hit Bowser Bowser can hit Mario Mario dies if at any point hits on Mario > hits on Bowser + 2
108. 108. FIRST TRY case class Hits(mario:Int, bowser:Int) def hitBowser(hits: Hits) = hits.copy(bowser = hits.bowser + 1) def hitMario(hits: Hits) = hits.copy(mario = hits.mario + 1) def marioWins = hitMario _ andThen hitBowser andThen hitBowser andThen hitBowser marioWins(Hits(0,0)) //Hits = Hits(1,3)
109. 109. 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
110. 110. 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?
111. 111. THE HITS ARE TRAPPED IN THE CAGE!!