Functions, Types, Programs and Effects

81
FUNCTIONS, TYPES, PROGRAMS AND EFFECTS

Transcript of Functions, Types, Programs and Effects

FUNCTIONS, TYPES, PROGRAMS AND EFFECTS

QUIZHANDS UP IF YOU RECOGNIZE

A => BA => 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.SemiGroupscalaz.Monoidscalaz.PlusEmpty

scalaz.Functorscalaz.Applicativescalaz.Monad

scalaz.ApplicativePlusscalaz.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 ProgramsUnsafe: Effects, running Programs

WRITING FUNCTIONAL SCALA

1. Write functions, which return values2. Choose effects (or write your own)

3. Construct a program that composes values into effects4. 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 blindnessFunctions 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 Errorcase object UserNotFound extends Errorcase object UserNameNotFound extends Errorcase 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.rightval 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 Xval res: String \/ Int = "Error!".leftval 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).

Givendef 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 / pseudoval append: (F,F) => Fval zero: F

//actualdef append(f1: F, f2: => F): Fdef zero: F

COMBINING EFFECTSMonad - 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 effectdef 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 EitherTval 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 Programval 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 typesealed trait TopicsError

sealed trait FindTopicError extends TopicsError

sealed trait SelectTopicError extends TopicsError

sealed trait DeleteTopicError extends TopicsError

Example programval 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 laterprogram.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 Monadcase class Topics[+E, +T](run: (TopicBroker, ExecutionContext) ⇒ Future[E \/ T]) { // more to follow

Roll your own Monadcase 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 inTopics((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 methodsobject 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 knowF[G[A, B]] => (F[A], F[B])

F = ListG[A,B] = Error \/ Result4

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 functionThen 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 VALUESUse data types like A \/ B that do not lose information about what

happened

Boolean blindness

'Option flatMap' blindness?

PROGRAMSChoose a reasonable architecture to construct your Programs

Monad Wrappers

Monad Transformers

Effect Systems

EOF