Upload
oliver-daff
View
1.409
Download
1
Embed Size (px)
Citation preview
FUNCTORS, APPLY,APPLICATIVE AND
MONADS
THAT'S JUST DULL
SO LET'S TRY INSTEAD ...
.
WHY SHOULD YOUCARE?
How do Functors, Apply, Applicative and Monad help youachieve early and continuous delivery of software?
3 CONCEPTS
IF A PIZZA COSTS $16 AND YOU BUYTWO HOW MUCH DO YOU SPEND?
A MARIO KART COSTS $20 PER DAYTO RENT. HOW MUCH DOES IT COST
TO RENT FOR TWO DAYS?
DON'T JUST THINK ABOUT THEMATHS
Think about the process you are going through before youget to the maths.
What information are you keeping and what information areyou dropping?
.
ABSTRACTIONAbstraction is an emphasis on the idea, qualities and
properties rather than the particulars
The importance of abstraction is derived from its ability tohide irrelevant details
It doesn't matter if its pizza, mario carts or anything else wetake the cost and muliply it by some factor.
WHAT IS THE REALATION SHIPBETWEEN A POLOGON:
A 3 sided triangleA 4 sided quadrilateral
WHAT HAS AM × AN = AM+N GOT INCOMMON WITH:
a2 × a3 = (a × a) × (a × a × a) = a5
a3 × a4 = (a × a × a) × (a × a × a × a) = a7
.
GENERALIZATIONRelationship that holds between all members of some set of
objects
IS A MILE A LONG WAY?
IS A YEAR A LARGE AMOUNT OFTIME?
.
IT ALL DEPENDS ONCONTEXT
A mile is along way if you are an Ant but not if you areflying in an aeroplane.A year is a long time for a Mayfly that lives for only 5minutes. But in geological time it's insignificant.
TRAINING LEVEL
WHAT IS FUNCTIONALPROGRAMMING?
Construct our programs using only purefunctions.
Pure functions have no side effects.
WHY IS A FUNCTIONLIKE A PIPE?
Some thing goes into one end and something else comesout the other end
Simple pipes simple can be joined together to form complexsystems?
WHAT'S SO GOOD ABOUT NO SIDEEFFECTS?
It makes it easier to reason about what's going on
IT'S IMPORTANT THAT FUNCTIONSLIKE PIPES DON'T LEAK
WORLD 1-1Functor Land
GOOMBA PROBLEM
HOW TO DEFEAT A GOOMBA
Stomp it and it turns into a coincase class Coin()case class Goomba()
def stomp(g:Goomba) = Coin()
The function stomp is our pipe, that transforms from aGoomba to a Coin.
LUIGIS VACUUM TO COLLECTGOOMBAS
class Vacuum { def collect(g:Goomba) = stomp(s)}
val vacuum = new Vacuum()val goomba = new Goomba()vacuum.collect(goomba)//Coin()
WHAT HAPPENS WHEN THEGOOMBA ESCAPES THE SUCTION?val vacuum = new Vacuum()
vacuum.collect(null)
.
CHECK FOR NULLSclass Vacuum { def collect(s:Goomba) = if (s == null) null else stomp(s)}
But then the calling class runs the risk of NullPointerexceptions.
There must be a better way
SOLUTIONPut the Goomba in a Cage
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]()
.
CAN WE GENERALIZETHE VACUUM CLASS?
WHY LIMIT OURSELF TO JUSTSTOMPING 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))
}}
Turn it into a traittrait 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)) }}
Parameterise the traittrait 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)) }}
.
THE FUNCTORA functor is basically for things that can be
mapped over.
THE FUNCTORDEFINITION IN SCALAZpackage 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] ...}
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)
IS THERE A BETTERWAY?
.
TYPE CLASSES IN 60SECONDS
WHY?Extend existing classesWithout inheritanceWithout altering original sourceKeeps concerns seperate
HOW?3 COMPONENTS
1. The type class2. Instances for particular types3. Interface methods for the api
THE TYPE CLASSProvide a generic type of what we want to implement.trait ProtoBuffWriter[A] { def write(value: A): Array[Byte]}
TYPE CLASS INSTANCESProvide implementations for the types we care about.
Create concrete implementations of the type class and markthem as implicit.
object DefaultProtoBuffWriters { implicit val coinWriter = ProtoBuffWriter[Coin] { .... } implicit val goombaWriter = ProtoBuffWriter[Goomba] { .... } // etc ...}
INTERFACESWhat is exposed to clients.
Generic methods that accept instances of the type class asimplicit params.
TWO WAYS OF DOING IT
INTERFACE OBJECTSAll 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())
INTERFACE SYNTAXPimp 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
WHAT ABOUT SCALAZ?Pimp existing types
Uses the Type classes in Ops classes
Ops classes use the Type class and provide more methodsimport scala.language.implicitConversionssealed 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 onlyclass 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))}
!!WARNING!!Implicits like warp pipes can be dangerous
SCALAZ FUNCTOR SYNTAX:TOFUNCTOROPS
scalaz.syntax.FunctorSyntax.scalatrait 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 theFunctorOps to F[A]
SCALAZ FUNCTOR SYNTAX:FUNCTOROPS
scalaz.syntax.FunctorSyntax.scalafinal 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 themap method to the Functor[F] in scope
FINALLYscalaz.syntax package object extends Syntaxes
trait Syntaxes { object functor extends ToFunctorOps}import scalaz.syntax.functor
CAGE FUNCTOR AGAINscalaz.syntax.FunctorSyntax.scala
import scalaz.Functorimplicit 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.functorCage(Goomba()).map(stomp)
THE FUNCTOR LAWSMapping preserves identity
If we map the id function over a functor, thefunctor that we get back should be the
same as the original functor
Mapping respects composition
Composing two functions and thenmapping the resulting function over a
functor should be the same as firstmapping one function over the functor and
then mapping the other one
DOES YOUR FUNCTOR BREAK LAWS?import org.scalacheck.Arbitraryimport org.specs2.scalaz.Specimport scalaz.Equalimport 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")
REMEMBER THE THREE POINTS?Abstraction: Functor is a abstract conceptGeneralization: There is a set of objects that can bemapped over
What about the context?
CONTEXTContext is the environment the function is applied in.
MEET SOME MORECONTEXTS
SCALA.OPTIONsealed 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 traitimport scalaz.std.option._import scalaz.std.anyVal._
checkAll(ScalazProperties.functor.laws[Option])
Option is context for a computation that might fail
LIST ARE ALSO FUNCTORSsealed abstract class List[+A] { ....final def map[B](f: (A) ⇒ B): List[B] ...}
Scalaz provides implicits to convert List to a Functor traitimport scalaz.std.list._import scalaz.std.anyVal._import org.specs2.scalaz.Specimport 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
DISJUNCTIONS ARE FUNCTORSimport org.specs2.scalaz.Specimport 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, α]})#λ])}
SO WHY IS THIS SOHANDY?
ORDINARY FUNCTIONS ARE SIMPLERTO:
readwriteusereason about
FUNCTIONS IN A CONTEXT HAVEUSEFUL PROPERTIES
Functors let us write ordinary functions
Then promote those functions into every context that mightneed that code
As new contexts arise we just define new functors topromote our ordinary code to work in those contexts.
///Ordinary functiondef stomp(g:Goomba) = g.stomp()
///The contextsealed 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 contextimport scalaz.Functorimplicit 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)) }}
///useimport scalaz.syntax.functorCage(Goomba()).map(stomp)
.
WORLD 1-2Apply Land
PIRANHA PLANT
PIRANHA PLANTcase class Coin()case class Fireball()case class PiranhaPlant()
def shoot(plant:PiranhaPlant, fireball:Fireball): Coin = Coin()
THE PLANT IS GENERATED FROM AUNRELIABLE SOURCE
Wrap the plant in a Cage and map over it?Cage(PiranhaPlant()).map(shoot _)
<console>:31: error: type mismatch;found : (PiranhaPlant, Fireball) => Coinrequired: PiranhaPlant => ? Cage(PiranhaPlant()).map(shoot _) </console>
CURRING
CURRING IS PARTIAL APPLICATIONTranslating the evaluation of a function that takes multiple
arguments into evaluating a sequence of functions, eachwith a single argument
(shoot _).curried//res41: PiranhaPlant => (Fireball => Coin) = <function1> </function1>
MAP THE CURRIED SHOOT FUNCTIONCage(PiranhaPlant()) map {shoot _}.curried//Cage[Fireball => Coin] = ...
WHAT IF THE FIREBALL PARAMETERGENERATION IS IN A CONTEXT?
Functor only support mapping functions over functordef map[A, B](fa: F[A])(f: A => B): F[B]
We need to map function in a functor over a value in afunctor
APPLYpackage scalaztrait Apply[F[_]] extends Functor[F] { self => //// def ap[A,B](fa: => F[A])(f: => F[A => B]): F[B] ...}
package scalazpackage 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)...}
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)}
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]()
WHAT HAVE WE DONE?Taken a function that takes two values.
Turned it into a function that takes two values in a context.
TESTING THE LAWSimport org.scalacheck.Arbitraryimport org.specs2.scalaz.Specimport scalaz.Equalimport 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])}
OPTION APPLY: OPTIONINSTANCESpackage scalazpackage 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 }
SHOOT THAT PIRANHA PLANTimport scalaz.std.option._
val partialShoot = Option(PiranhaPlant()) <*> Option((shoot _).curried)val optCoin = Option(Fireball()) <*> partialShoot//optCoin: Option[Coin] = Some(Coin())
SOME NICER SYNTAXimport scalaz.std.option._import scalaz.syntax.apply._
(̂Option(PiranhaPlant()), Option(Fireball()))(shoot)//res69: Option[Coin] = Some(Coin())
import scalaz.ApplyApply[Option].lift2(shoot)(Option(PiranhaPlant()), Option(Fireball()))//res70: Option[Coin] = Some(Coin())
LIST AS AN APPLY CONTEXTval partial = List(PiranhaPlant(), PiranhaPlant(), PiranhaPlant()) <*> List((shoot _).curried)List(Fireball()) <*> partial//res23: List[Coin] = List(Coin(), Coin(), Coin())
DISJUNCTION AS AN APPLY\/.right[String, Goomba](Goomba()) <*> \/.right[String, Goomba => Coin]((_:Goomba) => Coin()) //res: scalaz.\/[String,Coin] = \/-(Coin())
Apply lets you take a function that takes values and turn itinto a function that takes values in a context.
Write the code once and reuse it in the context you need
REMEMBER THE THREE POINTS?Abstraction: Apply is a abstract conceptGeneralization: There is a set of objects that implementthe Apply traitContext: How the funciton is used depends on the ApplySpecialization we are using
.
WORLD 1-3Applicative
KOOPA PARATROOPA
HAS TO BE:1. Koopa Paratroopa shot to a Koopa Troopa2. Koopa Troopa is shot to a Shell3. Shell is shot to a Coin
.
THE CODEcase 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())
APPLICATIVEtrait 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 mapoverride def map[A, B](fa: F[A])(f: A => B): F[B] = ap(fa)(point(f))
USING APPLICATIVEimport 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())
SAME CODE DIFFERENT CONTEXTval 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())
REMEMBER1. Abstraction: The Applicative2. Generalisation: The Applicative trait3. The context: Different behaviours for the same code
.
WORLD 1-4Monad
BOWSER
THE RULESMario can hit Bowser
Bowser can hit Mario
Mario dies if at any point hits on Mario > hits on Bowser + 2
FIRST TRYcase 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)
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
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?
THE HITS ARE TRAPPED IN THE CAGE!!
MONAD
MONADtrait Bind[F[_]] extends Apply[F] { self => def bind[A, B](fa: F[A])(f: A => F[B]): F[B]
override def ap[A, B](fa: => F[A])(f: => F[A => B]): F[B] = { lazy val fa0 = fa bind(f)(map(fa0)) } ...}trait Monad[F[_]] extends Applicative[F] with Bind[F] { self =>... override def map[A,B](fa: F[A])(f: A => B) = bind(fa)(a => point(f(a)))...}
Define point and bind and we get map and ap for free
CAGE MONADimplicit object CageMonad extends Monad[Cage]{ override def bind[A, B](fa: Cage[A])(f: (A) => Cage[B]): Cage[B] = fa match { case FullCage(a) => f(a) case EmptyCage() => EmptyCage[B]() }
override def point[A](a: => A): Cage[A] = Cage(a)}
BINDOPSfinal class BindOps[F[_],A] private[syntax](val self: F[A]) (implicit val F: Bind[F]) extends Ops[F[A]] {
...
def flatMap[B](f: A => F[B]) = F.bind(self)(f)
def >>=[B](f: A => F[B]) = F.bind(self)(f) ...}
trait ToBindOps extends ToBindOps0 with ToApplyOps { implicit def ToBindOps[F[_],A](v: F[A])(implicit F0: Bind[F]) = new BindOps[F,A](v)}
NOWimport scalaz.syntax.monad._
Cage(Hits(0,0)) >>= hitMario2 >>= hitMario2 >>= hitMario2 >>= hitBowser2 >>= hitBowser2 >>= hitBowser2//Cage[Hits] = EmptyCage()Cage(Hits(0,2)) >>= hitMario2 >>= hitMario2 >>= hitMario2 >>= hitBowser2 >>= hitBowser2 >>= hitBowser2//Cage[Hits] = FullCage(Hits(3,5))
FOR COMPREHENSION FLAT MAPval x = for { r1 <- Cage(Hits(0,0)) r2 <- hitMario2(r1) r3 <- hitMario2(r2) r4 <- hitMario2(r3) r5 <- hitBowser2(r4) r6 <- hitBowser2(r5) result <- hitBowser2(r6)} yield result
OTHER MONADSdef addTwo(x:Int) = Some(x + 2)1.some >>= addTwo//addTwo: (x: Int)List[Int]
def addTwo(x:Int) = List(x + 2)List(1,2,3) >>= addTwo//List[Int] = List(3, 4, 5)
Reader Monad
Writer Monad
State Monad
MONAD LAWSInherit the laws from Bind and Applicative
Right IdentityLeft Identity
HANG ON I CAN'T REUSE THESEFUNCTIONS
def hitMario3[F[_]](hits: Hits)(implicit F0: Monad[F]) :F[Hits] = hits match { case ko:Hits if ko.mario + 1 - ko.bowser > 2 => F0.point(null: Hits) case Hits(mario, bowser) => F0.point(Hits(mario + 1, bowser))}
def hitBowser3[F[_]](hits: Hits)(implicit F0: Monad[F]) :F[Hits] = hits match { case ko:Hits if ko.mario + 1- ko.bowser > 2 => F0.point(null: Hits) case Hits(mario, bowser) => F0.point(Hits(mario, bowser + 1))}
Cage(Hits(1,2)) >>= hitMario3[Cage]//Cage[Hits] = FullCage(Hits(2,2))Cage(Hits(1,2)) >>= hitBowser3[Cage]//Cage[Hits] = FullCage(Hits(1,3))
MONADLets us call a function that takes a value and returns a value
in a context with a value in a context.
REMEMBER1. Abstraction: The Monad2. Generalisation: The Monad trait3. The context: Different behaviours for the same code
SUMMARY
Functor
Apply
Applicative
Monad
def map[A, B](fa: F[A])(f: A => B): F[B]
def ap[A,B](fa: => F[A]) (f: => F[A => B]): F[B]
def point[A](a: => A): F[A]
def bind[A, B](fa: F[A]) (f: A => F[B]): F[B]
REFERENCESLearn 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/
.