FUNCTIONS, TYPES, PROGRAMS AND EFFECTS
QUIZHANDS UP IF YOU RECOGNIZE
A => B A => Option[B]
Either[A,B]
A / B A Xor B
A => M[A]
scala.concurrent.Future[+A]
scalaz.concurrent.Task[+A]
What does this do? (maybeInt1 |@| maybeInt2) { _ > _ } | false
scalaz.SemiGroup scalaz.Monoid scalaz.PlusEmpty
scalaz.Functor scalaz.Applicative scalaz.Monad
scalaz.ApplicativePlus scalaz.MonadPlus
Does SI-2712 ring a bell? scalaz.Unapply[TC[_[_]], MA] traverseU
EitherT[F[_], A, B] OptionT[F[_], A]
What's this all about? type E = Task |: (String / ?) |: Option |: Base type Stack = ReaderInt |: WriterString |: Eval |: NoEffect type PRG[A] = (Log.DSL :@: DB.DSL :@: FXNil)#Cop[A] Free[PRG, Xor[DBError, Entity]]
Do you use scalaz or cats for your day job?
FUNCTIONS, TYPES, PROGRAMS AND EFFECTS Strict separation of safe and unsafe Safe: Functions, Types, creating Programs Unsafe: Effects, running Programs
WRITING FUNCTIONAL SCALA 1. Write functions, which return values 2. Choose effects (or write your own) 3. Construct a program that composes values into effects 4. Run the program (in an 'App' in the main method) (Not necessarily in that order)
WRITING FUNCTIONAL SCALA ▸ You can possibly have programs of programs ▸ A program is very often defined in a for comprehension
A hypothetical example. val program = for { client <- server.connect(details) Exchange(src, snk) = client.exchange _ <- snk.sendRequest(request) in = src.pipe(text.utf8Decode) .to(io.stdOutLines) } yield () program.run Define first, run later.
BUT FIRST: SOMETHING ABOUT INFORMATION LOSS
QUIZKEEP IN MIND: FUNCTIONS, TYPES, PROGRAMS, EFFECTS
Anything 'wrong' with these methods / functions? def getUser(username: Username): Future[User] def createUser(details: UserDetails): Future[User] def getPrimaryAccount(user: User): Future[Account]
Anything 'wrong' with this? def getUser(username: Username): Future[Option[User]] def getAccounts(user: User): Future[List[Account]] def approved(user: User, accounts: List[Account]): Future[Boolean]
DATA LOSS Boolean blindness Functions return values, discarding them constrains the Program 1 1 Think of the information you need later on in a for comprehension
What is 'wrong' with this? sealed trait Error case object UserNotFound extends Error case object UserNameNotFound extends Error case object AccountNotFound extends Error def getUser(username: Username): Future[Either[Error, User]] def getAccounts(user: User): Future[Either[Error, Account]]
scalaz Disjunction (left or right) val res: String / Int = /-(5) val res1: String / Int = 5.right val moreVerbose: String / Int = 5.right[String] val res2: String / Int = "Some error".left val friendlier = res2.leftMap(error => s"$error has occured, we apologize.") val res3 = res1.map(five => five * 2) // /-(10) Map over the right
From Throwable to / /.fromTryCatchThrowable[String, Exception] { getDangerousString() } // returns a Exception / String /.fromTryCatchThrowable[String, Exception] { getDangerousString() }.leftMap(e=> MyError(e)) // returns a MyError / String
From A / B to X val res: String / Int = "Error!".left val res1 = res.fold(left=> 0, r => r) val res2 = res.fold(left=> 0, identity) // this is the same Why should you not use this?
Combining / case class Error(in: Int, reason: String) def even(in: Int): Error / Int = if(in % 2 == 0) in.right else Error(in, "not even").left def divByThree(in: Int): Error / Int = if(in % 3 == 0) in.right else Error(in, "not div 3").left def evenAndByThree(in: Int) = for { evenNr <- even(in) byThree <- divByThree(evenNr) } yield byThree println(evenAndByThree(12)) // /-(12) println(evenAndByThree(3)) // -/-(3, "not even") println(evenAndByThree(4)) // -/-(4, "not div 3")
def evenAndByThree(in: Int) = for { evenNr <- even(in) byThree <- divByThree(evenNr) } yield byThree No loss of information (why the first error occurred).
Given def getUser(username: Username): Future[Error / User]] def getAccounts(user: User): Future[Error / List[Account]] Does this compile? val result = for { user <- getUser(name) accounts <- getAccounts(user) } yield accounts
COMBINING EFFECTS
Combining values Monoid - collecting / combining values into one value //simplified / pseudo val append: (F,F) => F val zero: F //actual def append(f1: F, f2: => F): F def zero: F
COMBINING EFFECTS Monad - collecting / combining effects 2 def point[A](a: => A): F[A] // Creates an 'effects collector' def bind[A, B](fa: F[A])(f: A => F[B]): F[B] // adds an effect def map[A,B](fa: F[A])(f: A => B):F[B] // transforms input for next effect Can only combine for same type of F.2 2 The Monad is a monoid in the category of endofunctors joke
In scalaz, flatMap is defined as: def flatMap[B](f: A => F[B]) = F.bind(self)(f) flatMap ~= bind
Monads are great, but in general NOT composable. Monad Transformers Effect systems (i.e. Eff from eff-cats) In common, a Type that combines N Monad types
Monad Transformer EitherT val res = for { user <- EitherT(getUser("bla")) accounts <- EitherT(getAccounts(user)) } yield accounts // returns EitherT[Future, Error, List[Account]] res.run // Future[Error / List[Account]] Scalaz provides Future Monad instance in scalaz.std.scalaFuture
Construct the Program val res = for { user <- EitherT(getUser("bla")) accounts <- EitherT(getAccounts(user)) } yield accounts Run the Program res.run
Sometimes... a Monad Wrapper is a good enough Program
WRITE YOUR OWN MONAD WRAPPER 3 3 specific monad transformer?
WRITE YOUR OWN MONAD WRAPPER Topics (Kafka library) Combine Future and / Covariant +A/+B Know why a Kafka operation failed in a Program
Why not EitherT? final case class EitherT[F[_], A, B](run: F[A / B]) Invariant in A,B
Covariant Error type sealed trait TopicsError sealed trait FindTopicError extends TopicsError sealed trait SelectTopicError extends TopicsError sealed trait DeleteTopicError extends TopicsError
Example program val program: Topics[TopicsError, Chunk[String, String]] = for { partition ← Topics.partitioned[String, String](topicDef, partitionId) info ← partition.write(record) chunk ← partition.read(info.offset, info.offset) } yield chunk // ... somewhere later program.run(broker, executionContext)
Minor detour (type lambda) Monad[{ type λ[α] = Topics[E, α] })#λ] ~= type MyMonad[T] = Monad[Topics[E, T]] only inline.
Roll your own Monad instance (Scalaz) object Topics { implicit def topicsMonad[E] = new Monad[({ type λ[α] = Topics[E, α] })#λ] { def point[T](value: T): Topics[E, T] = Topics.point(value) def bind[T, U](topics: Topics[E, T])(f: T Topics[E, U]): Topics[E, U] = topics.flatMap(f) } } delegate to point and flatMap
Roll your own Monad case class Topics[+E, +T](run: (TopicBroker, ExecutionContext) Future[E / T]) { // more to follow
Roll your own Monad case class Topics[+E, +T](run: (TopicBroker, ExecutionContext) Future[E / T]) { def map[C](f: T C): Topics[E, C] = Topics((broker, ec) run(broker, ec).map(_.map(f))(ec)) def flatMap[E1 >: E, C](f: T Topics[E1, C]): Topics[E1, C] = Topics { (broker, ec) run(broker, ec).flatMap( _.fold(err Future.successful(-/(err)), res f(res).run(broker, ec)) )(ec) } }
map
case class Topics[+E, +T](run: (TopicBroker, ExecutionContext) Future[E / T]) { def map[C](f: T C): Topics[E, C] = Topics((broker, ec) run(broker, ec).map(_.map(f))(ec)) zoom in Topics((broker, ec) run(broker, ec).map(_.map(f))(ec)) zoom in run(broker, ec).map(_.map(f))
flatMap
Topics { (broker, ec) run(broker, ec).flatMap( _.fold(err Future.successful(-/(err)), res f(res).run(broker, ec)) )(ec) } zoom in run(broker, ec).flatMap( _.fold(err Future.successful(-/(err)), res f(res).run(broker, ec)) )(ec) zoom in fold(err Future.successful(-/(err)), res f(res).run(broker, ec))
Convenience methods object Topics { def run[E, T](broker: TopicBroker, action: Topics[E, T])(implicit ec: ExecutionContext): Future[E / T] = action.run(broker, ec) def point[T](value: T): Topics[Nothing, T] = Topics[Nothing, T]((broker, ec) Future.successful(value.right)) def future[T](action: Future[T]): Topics[Nothing, T] = Topics((broker, ec) action.map(_.right)(ec)) def futureF[T](action: ExecutionContext Future[T]): Topics[Nothing, T] = Topics((broker, ec) action(ec).map(_.right)(ec)) def either[E, T](either: E / T): Topics[E, T] = Topics((_, _) Future.successful(either)) def futureEither[E, T](action: Future[E / T]): Topics[E, T] = Topics((_, _) action) def futureEitherF[E, T](action: ExecutionContext Future[E / T]): Topics[E, T] = Topics((_, ec) action(ec)) // ... more code }
What about Monad Laws?
import scalaz.scalacheck.ScalazProperties.monad class MonadLawsSpec extends Spec { def is = s2"""obey the monad laws $laws""" def laws = { implicit val b = broker monad.laws[({ type l[a] = Topics[Int, a] })#l] } }
Another Program example opt-parse-applicative "net.bmjames" %% "scala-optparse-applicative" % "0.3"
case class Config(inputFile: Option[File], outputFile: Option[File]) def main(args: Array[String]): Unit = { val config = parseArgs(args) // ... }
val inputFile = optional(opt[File]( ensure[File](readStr.map(str new File(str)), "INPUT_FILE must be an existing file", _.isFile), long("input-file"), metavar("INPUT_FILE"), short('f'), help("input file to read from") )) val outputFile = optional(opt[File]( readStr.map(str new File(str)), long("output-file"), metavar("OUTPUT_FILE"), short('o'), help("output file to write to") ))
def parseArgs(args: Array[String]): Config = { val inputFile = optional(opt[File]( // ... omitted val outputFile = optional(opt[File]( // ... omitted val parser = (input |@| inputFile)(Config.apply(_, _)) execParser(args, "copy", info(parser <*> helper, header("The awesome copy utility.") )) }
Define the program val parser = (input |@| inputFile)(Config.apply(_, _)) Execute the program execParser(args, "copy", info(parser <*> helper, header("The awesome copy utility.") ))
Some more thoughts
Scalaz has virtually no docs )-:
Scalaz has really cool stuff (-:
On a lazy Sunday.. trait MonadPlus[F[_]] extends Monad[F] with ApplicativePlus[F] { self => //... /** Generalized version of Haskell's `partitionEithers` */ def separate[G[_, _], A, B](value: F[G[A, B]])(implicit G: Bifoldable[G]): (F[A], F[B]) = { val lefts = bind(value)((aa) => G.leftFoldable.foldMap(aa)(a => point(a))(monoid[A])) val rights = bind(value)((bb) => G.rightFoldable.foldMap(bb)(b => point(b))(monoid[B])) (lefts, rights) } //... final class MonadPlusOps[F[_],A] private[syntax](val self: F[A])(implicit val F: MonadPlus[F]) extends Ops[F[A]] { final def separate[G[_, _], B, C](implicit ev: A === G[B, C], G: Bifoldable[G]): (F[B], F[C]) = F.separate(ev.subst(self)) //... You read some code..
WTF
But, separate is very useful. def separate[G[_, _], A, B](value: F[G[A, B]])(implicit G: Bifoldable[G]): (F[A], F[B]) Remove everything, keep args and return value (value: F[G[A, B]]): (F[A], F[B])
(value: F[G[A, B]]): (F[A], F[B]) Remove remaining 'syntax' F[G[A, B]] => (F[A], F[B])
Lets substitute F, G, A and B to something we know F[G[A, B]] => (F[A], F[B]) F = List G[A,B] = Error / Result 4 4 / uses infix type notation, same as /[Error,Result]
List[Error / Result] => (List[Error], List[Result]) It's a function to separate the errors from the results!
There are many of these. How do you find a concrete function if they are defined in the abstract?
DIG Remove all syntax until you are left with a function Then find which implicits / type classes are needed for the function. Scalaz requires you to know how the typeclasses are organized
cats project has more docs An introduction to cats http://typelevel.org/cats/ Advanced Scala with Cats book 5 http://underscore.io/blog 5 http://underscore.io/books/advanced-scala/
Functional Programming in Scala (the red book) 6 6 https://www.manning.com/books/functional-programming-in-scala
http://typelevel.org/blog (Some articles are really advanced)
RECAP
WRITING FUNCTIONAL SCALA ▸ Write functions ▸ Choose effects (or write your own) ▸ Construct a program that composes the functions and effects ▸ Run the program (in an 'App' in the main method)
RETURN VALUES Use data types like A / B that do not lose information about what happened Boolean blindness 'Option flatMap' blindness?
PROGRAMS Choose a reasonable architecture to construct your Programs Monad Wrappers Monad Transformers Effect Systems
EOF
Functions, Types, Programs and Effects

Functions, Types, Programs and Effects

  • 1.
  • 2.
    QUIZHANDS UP IFYOU RECOGNIZE
  • 3.
    A => B A=> Option[B]
  • 4.
  • 5.
    A / B AXor B
  • 6.
  • 7.
  • 8.
  • 9.
    What does thisdo? (maybeInt1 |@| maybeInt2) { _ > _ } | false
  • 10.
  • 11.
  • 12.
  • 13.
    Does SI-2712 ringa bell? scalaz.Unapply[TC[_[_]], MA] traverseU
  • 14.
  • 15.
    What's this allabout? type E = Task |: (String / ?) |: Option |: Base type Stack = ReaderInt |: WriterString |: Eval |: NoEffect type PRG[A] = (Log.DSL :@: DB.DSL :@: FXNil)#Cop[A] Free[PRG, Xor[DBError, Entity]]
  • 16.
    Do you usescalaz or cats for your day job?
  • 17.
    FUNCTIONS, TYPES, PROGRAMSAND EFFECTS Strict separation of safe and unsafe Safe: Functions, Types, creating Programs Unsafe: Effects, running Programs
  • 18.
    WRITING FUNCTIONAL SCALA 1.Write functions, which return values 2. Choose effects (or write your own) 3. Construct a program that composes values into effects 4. Run the program (in an 'App' in the main method) (Not necessarily in that order)
  • 19.
    WRITING FUNCTIONAL SCALA ▸You can possibly have programs of programs ▸ A program is very often defined in a for comprehension
  • 20.
    A hypothetical example. valprogram = for { client <- server.connect(details) Exchange(src, snk) = client.exchange _ <- snk.sendRequest(request) in = src.pipe(text.utf8Decode) .to(io.stdOutLines) } yield () program.run Define first, run later.
  • 21.
  • 22.
    QUIZKEEP IN MIND:FUNCTIONS, TYPES, PROGRAMS, EFFECTS
  • 23.
    Anything 'wrong' withthese methods / functions? def getUser(username: Username): Future[User] def createUser(details: UserDetails): Future[User] def getPrimaryAccount(user: User): Future[Account]
  • 24.
    Anything 'wrong' withthis? def getUser(username: Username): Future[Option[User]] def getAccounts(user: User): Future[List[Account]] def approved(user: User, accounts: List[Account]): Future[Boolean]
  • 25.
    DATA LOSS Boolean blindness Functionsreturn values, discarding them constrains the Program 1 1 Think of the information you need later on in a for comprehension
  • 26.
    What is 'wrong'with this? sealed trait Error case object UserNotFound extends Error case object UserNameNotFound extends Error case object AccountNotFound extends Error def getUser(username: Username): Future[Either[Error, User]] def getAccounts(user: User): Future[Either[Error, Account]]
  • 27.
    scalaz Disjunction (leftor right) val res: String / Int = /-(5) val res1: String / Int = 5.right val moreVerbose: String / Int = 5.right[String] val res2: String / Int = "Some error".left val friendlier = res2.leftMap(error => s"$error has occured, we apologize.") val res3 = res1.map(five => five * 2) // /-(10) Map over the right
  • 28.
    From Throwable to/ /.fromTryCatchThrowable[String, Exception] { getDangerousString() } // returns a Exception / String /.fromTryCatchThrowable[String, Exception] { getDangerousString() }.leftMap(e=> MyError(e)) // returns a MyError / String
  • 29.
    From A /B to X val res: String / Int = "Error!".left val res1 = res.fold(left=> 0, r => r) val res2 = res.fold(left=> 0, identity) // this is the same Why should you not use this?
  • 30.
    Combining / case classError(in: Int, reason: String) def even(in: Int): Error / Int = if(in % 2 == 0) in.right else Error(in, "not even").left def divByThree(in: Int): Error / Int = if(in % 3 == 0) in.right else Error(in, "not div 3").left def evenAndByThree(in: Int) = for { evenNr <- even(in) byThree <- divByThree(evenNr) } yield byThree println(evenAndByThree(12)) // /-(12) println(evenAndByThree(3)) // -/-(3, "not even") println(evenAndByThree(4)) // -/-(4, "not div 3")
  • 31.
    def evenAndByThree(in: Int)= for { evenNr <- even(in) byThree <- divByThree(evenNr) } yield byThree No loss of information (why the first error occurred).
  • 32.
    Given def getUser(username: Username):Future[Error / User]] def getAccounts(user: User): Future[Error / List[Account]] Does this compile? val result = for { user <- getUser(name) accounts <- getAccounts(user) } yield accounts
  • 33.
  • 34.
    Combining values Monoid -collecting / combining values into one value //simplified / pseudo val append: (F,F) => F val zero: F //actual def append(f1: F, f2: => F): F def zero: F
  • 35.
    COMBINING EFFECTS Monad -collecting / combining effects 2 def point[A](a: => A): F[A] // Creates an 'effects collector' def bind[A, B](fa: F[A])(f: A => F[B]): F[B] // adds an effect def map[A,B](fa: F[A])(f: A => B):F[B] // transforms input for next effect Can only combine for same type of F.2 2 The Monad is a monoid in the category of endofunctors joke
  • 36.
    In scalaz, flatMapis defined as: def flatMap[B](f: A => F[B]) = F.bind(self)(f) flatMap ~= bind
  • 37.
    Monads are great,but in general NOT composable. Monad Transformers Effect systems (i.e. Eff from eff-cats) In common, a Type that combines N Monad types
  • 38.
    Monad Transformer EitherT valres = for { user <- EitherT(getUser("bla")) accounts <- EitherT(getAccounts(user)) } yield accounts // returns EitherT[Future, Error, List[Account]] res.run // Future[Error / List[Account]] Scalaz provides Future Monad instance in scalaz.std.scalaFuture
  • 39.
    Construct the Program valres = for { user <- EitherT(getUser("bla")) accounts <- EitherT(getAccounts(user)) } yield accounts Run the Program res.run
  • 40.
    Sometimes... a MonadWrapper is a good enough Program
  • 41.
    WRITE YOUR OWNMONAD WRAPPER 3 3 specific monad transformer?
  • 42.
    WRITE YOUR OWNMONAD WRAPPER Topics (Kafka library) Combine Future and / Covariant +A/+B Know why a Kafka operation failed in a Program
  • 43.
    Why not EitherT? finalcase class EitherT[F[_], A, B](run: F[A / B]) Invariant in A,B
  • 44.
    Covariant Error type sealedtrait TopicsError sealed trait FindTopicError extends TopicsError sealed trait SelectTopicError extends TopicsError sealed trait DeleteTopicError extends TopicsError
  • 45.
    Example program val program:Topics[TopicsError, Chunk[String, String]] = for { partition ← Topics.partitioned[String, String](topicDef, partitionId) info ← partition.write(record) chunk ← partition.read(info.offset, info.offset) } yield chunk // ... somewhere later program.run(broker, executionContext)
  • 46.
    Minor detour (typelambda) Monad[{ type λ[α] = Topics[E, α] })#λ] ~= type MyMonad[T] = Monad[Topics[E, T]] only inline.
  • 47.
    Roll your ownMonad instance (Scalaz) object Topics { implicit def topicsMonad[E] = new Monad[({ type λ[α] = Topics[E, α] })#λ] { def point[T](value: T): Topics[E, T] = Topics.point(value) def bind[T, U](topics: Topics[E, T])(f: T Topics[E, U]): Topics[E, U] = topics.flatMap(f) } } delegate to point and flatMap
  • 48.
    Roll your ownMonad case class Topics[+E, +T](run: (TopicBroker, ExecutionContext) Future[E / T]) { // more to follow
  • 49.
    Roll your ownMonad case class Topics[+E, +T](run: (TopicBroker, ExecutionContext) Future[E / T]) { def map[C](f: T C): Topics[E, C] = Topics((broker, ec) run(broker, ec).map(_.map(f))(ec)) def flatMap[E1 >: E, C](f: T Topics[E1, C]): Topics[E1, C] = Topics { (broker, ec) run(broker, ec).flatMap( _.fold(err Future.successful(-/(err)), res f(res).run(broker, ec)) )(ec) } }
  • 50.
  • 51.
    case class Topics[+E,+T](run: (TopicBroker, ExecutionContext) Future[E / T]) { def map[C](f: T C): Topics[E, C] = Topics((broker, ec) run(broker, ec).map(_.map(f))(ec)) zoom in Topics((broker, ec) run(broker, ec).map(_.map(f))(ec)) zoom in run(broker, ec).map(_.map(f))
  • 52.
  • 53.
    Topics { (broker,ec) run(broker, ec).flatMap( _.fold(err Future.successful(-/(err)), res f(res).run(broker, ec)) )(ec) } zoom in run(broker, ec).flatMap( _.fold(err Future.successful(-/(err)), res f(res).run(broker, ec)) )(ec) zoom in fold(err Future.successful(-/(err)), res f(res).run(broker, ec))
  • 54.
    Convenience methods object Topics{ def run[E, T](broker: TopicBroker, action: Topics[E, T])(implicit ec: ExecutionContext): Future[E / T] = action.run(broker, ec) def point[T](value: T): Topics[Nothing, T] = Topics[Nothing, T]((broker, ec) Future.successful(value.right)) def future[T](action: Future[T]): Topics[Nothing, T] = Topics((broker, ec) action.map(_.right)(ec)) def futureF[T](action: ExecutionContext Future[T]): Topics[Nothing, T] = Topics((broker, ec) action(ec).map(_.right)(ec)) def either[E, T](either: E / T): Topics[E, T] = Topics((_, _) Future.successful(either)) def futureEither[E, T](action: Future[E / T]): Topics[E, T] = Topics((_, _) action) def futureEitherF[E, T](action: ExecutionContext Future[E / T]): Topics[E, T] = Topics((_, ec) action(ec)) // ... more code }
  • 55.
  • 56.
    import scalaz.scalacheck.ScalazProperties.monad class MonadLawsSpecextends Spec { def is = s2"""obey the monad laws $laws""" def laws = { implicit val b = broker monad.laws[({ type l[a] = Topics[Int, a] })#l] } }
  • 57.
    Another Program example opt-parse-applicative "net.bmjames"%% "scala-optparse-applicative" % "0.3"
  • 58.
    case class Config(inputFile:Option[File], outputFile: Option[File]) def main(args: Array[String]): Unit = { val config = parseArgs(args) // ... }
  • 59.
    val inputFile =optional(opt[File]( ensure[File](readStr.map(str new File(str)), "INPUT_FILE must be an existing file", _.isFile), long("input-file"), metavar("INPUT_FILE"), short('f'), help("input file to read from") )) val outputFile = optional(opt[File]( readStr.map(str new File(str)), long("output-file"), metavar("OUTPUT_FILE"), short('o'), help("output file to write to") ))
  • 60.
    def parseArgs(args: Array[String]):Config = { val inputFile = optional(opt[File]( // ... omitted val outputFile = optional(opt[File]( // ... omitted val parser = (input |@| inputFile)(Config.apply(_, _)) execParser(args, "copy", info(parser <*> helper, header("The awesome copy utility.") )) }
  • 61.
    Define the program valparser = (input |@| inputFile)(Config.apply(_, _)) Execute the program execParser(args, "copy", info(parser <*> helper, header("The awesome copy utility.") ))
  • 62.
  • 63.
  • 64.
    Scalaz has reallycool stuff (-:
  • 65.
    On a lazySunday.. trait MonadPlus[F[_]] extends Monad[F] with ApplicativePlus[F] { self => //... /** Generalized version of Haskell's `partitionEithers` */ def separate[G[_, _], A, B](value: F[G[A, B]])(implicit G: Bifoldable[G]): (F[A], F[B]) = { val lefts = bind(value)((aa) => G.leftFoldable.foldMap(aa)(a => point(a))(monoid[A])) val rights = bind(value)((bb) => G.rightFoldable.foldMap(bb)(b => point(b))(monoid[B])) (lefts, rights) } //... final class MonadPlusOps[F[_],A] private[syntax](val self: F[A])(implicit val F: MonadPlus[F]) extends Ops[F[A]] { final def separate[G[_, _], B, C](implicit ev: A === G[B, C], G: Bifoldable[G]): (F[B], F[C]) = F.separate(ev.subst(self)) //... You read some code..
  • 66.
  • 67.
    But, separate isvery useful. def separate[G[_, _], A, B](value: F[G[A, B]])(implicit G: Bifoldable[G]): (F[A], F[B]) Remove everything, keep args and return value (value: F[G[A, B]]): (F[A], F[B])
  • 68.
    (value: F[G[A, B]]):(F[A], F[B]) Remove remaining 'syntax' F[G[A, B]] => (F[A], F[B])
  • 69.
    Lets substitute F,G, A and B to something we know F[G[A, B]] => (F[A], F[B]) F = List G[A,B] = Error / Result 4 4 / uses infix type notation, same as /[Error,Result]
  • 70.
    List[Error / Result]=> (List[Error], List[Result]) It's a function to separate the errors from the results!
  • 71.
    There are manyof these. How do you find a concrete function if they are defined in the abstract?
  • 72.
    DIG Remove all syntaxuntil you are left with a function Then find which implicits / type classes are needed for the function. Scalaz requires you to know how the typeclasses are organized
  • 73.
    cats project hasmore docs An introduction to cats http://typelevel.org/cats/ Advanced Scala with Cats book 5 http://underscore.io/blog 5 http://underscore.io/books/advanced-scala/
  • 74.
    Functional Programming inScala (the red book) 6 6 https://www.manning.com/books/functional-programming-in-scala
  • 75.
  • 76.
  • 77.
    WRITING FUNCTIONAL SCALA ▸Write functions ▸ Choose effects (or write your own) ▸ Construct a program that composes the functions and effects ▸ Run the program (in an 'App' in the main method)
  • 78.
    RETURN VALUES Use datatypes like A / B that do not lose information about what happened Boolean blindness 'Option flatMap' blindness?
  • 79.
    PROGRAMS Choose a reasonablearchitecture to construct your Programs Monad Wrappers Monad Transformers Effect Systems
  • 80.