KEMBAR78
Functions, Types, Programs and Effects | PDF
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.