'

Functional Programming Patterns for the Pragmatic Programmer

Понравилась презентация – покажи это...





Слайд 0

Functional Programming Patterns (for the pragmatic programmer) ~ @raulraja CTO @47deg


Слайд 1

Acknowledgment • Scalaz • Rapture : Jon Pretty • Miles Sabin : Shapeless • Rúnar Bjarnason : Compositional Application Architecture With Reasonably Priced Monads • Noel Markham : A purely functional approach to building large applications • Jan Christopher Vogt : Tmaps


Слайд 2

Functions are first class citizens in FP Architecture


Слайд 3

I want my main app services to strive for • Composability • Dependency Injection • Interpretation • Fault Tolerance


Слайд 4

Composability Composition gives us the power to easily mix simple functions to achieve more complex workflows.


Слайд 5

Composability We can achieve monadic function composition with Kleisli Arrows A M[B] In other words a function that for a given input it returns a type constructor… List[B], Option[B], Either[B], Task[B], Future[B]…


Слайд 6

Composability When the type constructor M[_] it's a Monad it can be composed and sequenced in for comprehensions val composed = for { a <- Kleisli((x : String) Option(x.toInt + 1)) b <- Kleisli((x : String) Option(x.toInt * 2)) } yield a + b


Слайд 7

Composability The deferred injection of the input parameter enables Dependency Injection val composed = for { a <- Kleisli((x : String) Option(x.toInt + 1)) b <- Kleisli((x : String) Option(x.toInt * 2)) } yield a + b composed.run("1")


Слайд 8

Composability : Kleisli What about when the args are not of the same type? val composed = for { a <- Kleisli((x : String) b <- Kleisli((x : Int) } yield a + b Option(x.toInt + 1)) Option(x * 2))


Слайд 9

Composability : Kleisli By using Kleisli we just achieved • Composability • Dependency Injection • Interpretation • Fault Tolerance


Слайд 10

Interpretation : Free Monads What is a Free Monad? -- A monad on a custom ADT that can be run through an Interpreter


Слайд 11

Interpretation : Free Monads sealed trait Op[A] case class Ask[A](a: () A) extends Op[A] case class Async[A](a: () case class Tell(a: () A) extends Op[A] Unit) extends Op[Unit]


Слайд 12

Interpretation : Free Monads What can you achieve with a custom ADT and Free Monads? def ask[A](a: A): OpMonad[A] = Free.liftFC(Ask(() def async[A](a: def tell(a: a)) A): OpMonad[A] = Free.liftFC(Async(() Unit): OpMonad[Unit] = Free.liftFC(Tell(() a)) a))


Слайд 13

Interpretation : Free Monads Functors and Monads for Free (No need to manually implement map, flatMap, etc...) type OpMonad[A] = Free.FreeC[Op, A] implicit val MonadOp: Monad[OpMonad] = Free.freeMonad[({type λ[α] = Coyoneda[Op, α]})#λ]


Слайд 14

Interpretation : Free Monads At this point a program like this is nothing but Data describing the sequence of execution but FREE of it's runtime interpretation. val program = for { a <- ask(1) b <- async(2) _ <- tell(println("log something")) } yield a + b


Слайд 15

Interpretation : Free Monads We isolate interpretations via Natural transformations AKA Interpreters. In other words with map over the outer type constructor Op object ProdInterpreter extends (Op ~> Task) { def apply[A](op: Op[A]) = op match { case Ask(a) Task(a()) case Async(a) case Tell(a) } } Task.fork(Task.delay(a())) Task.delay(a())


Слайд 16

Interpretation : Free Monads We can have different interpreters for our production / test / experimental code. object TestInterpreter extends (Op ~> Id.Id) { def apply[A](op: Op[A]) = op match { case Ask(a) a() case Async(a) case Tell(a) } } a() a()


Слайд 17

Requirements • Composability • Dependency Injection • Interpretation • Fault Tolerance


Слайд 18

Fault Tolerance Most containers and patterns generalize to the most common super-type or simply Throwable loosing type information. val f = scala.concurrent.Future.failed(new NumberFormatException) val t = scala.util.Try(throw new NumberFormatException) val d = for { a <- 1.right[NumberFormatException] b <- (new RuntimeException).left[Int] } yield a + b


Слайд 19

Fault Tolerance We don't have to settle for Throwable!!! We could use instead… • Nested disjunctions • Coproducts • Delimited, Monadic, Dependently-typed, Accumulating Checked Exceptions


Слайд 20

Fault Tolerance : Dependentlytyped Acc Exceptions Introducing rapture.core.Result


Слайд 21

Fault Tolerance : Dependentlytyped Acc Exceptions Result is similar to \/ but has 3 possible outcomes (Answer, Errata, Unforeseen) val op = for { a <- Result.catching[NumberFormatException]("1".toInt) b <- Result.errata[Int, IllegalArgumentException]( new IllegalArgumentException("expected")) } yield a + b


Слайд 22

Fault Tolerance : Dependentlytyped Acc Exceptions Result uses dependently typed monadic exception accumulation val op = for { a <- Result.catching[NumberFormatException]("1".toInt) b <- Result.errata[Int, IllegalArgumentException]( new IllegalArgumentException("expected")) } yield a + b


Слайд 23

Fault Tolerance : Dependentlytyped Acc Exceptions You may recover by resolving errors to an Answer. op resolve ( each[IllegalArgumentException](_ each[NumberFormatException](_ 0), 0), each[IndexOutOfBoundsException](_ 0))


Слайд 24

Fault Tolerance : Dependentlytyped Acc Exceptions Or reconcile exceptions into a new custom one. case class MyCustomException(e : Exception) extends Exception(e.getMessage) op reconcile ( each[IllegalArgumentException](MyCustomException(_)), each[NumberFormatException](MyCustomException(_)), each[IndexOutOfBoundsException](MyCustomException(_)))


Слайд 25

Requirements We have all the pieces we need Let's put them together! • Composability • Dependency Injection • Interpretation • Fault Tolerance


Слайд 26

Solving the Puzzle How do we assemble a type that is: Kleisli + Custom ADT + Result for { a <- Kleisli((x : String) b <- Kleisli((x : String) ask(Result.catching[NumberFormatException](x.toInt))) ask(Result.catching[IllegalArgumentException](x.toInt))) } yield a + b We want a and b to be seen as Int but this won't compile because there are 3 nested monads


Слайд 27

Solving the Puzzle : Monad Transformers Monad Transformers to the rescue! type ServiceDef[D, A, B <: Exception] = ResultT[({type λ[α] = ReaderT[OpMonad, D, α]})#λ, A, B]


Слайд 28

Solving the Puzzle : Services Two services with different dependencies case class Converter() { def convert(x: String): Int = x.toInt } case class Adder() { def add(x: Int): Int = x + 1 } case class Config(converter: Converter, adder: Adder) val system = Config(Converter(), Adder())


Слайд 29

Solving the Puzzle : Services Two services with different dependencies def service1(x : String) = Service { converter: Converter ask(Result.catching[NumberFormatException](converter.convert(x))) } def service2 = Service { adder: Adder ask(Result.catching[IllegalArgumentException](adder.add(22) + " added ")) }


Слайд 30

Solving the Puzzle : Services Two services with different dependencies val composed = for { a <- service1("1").liftD[Config] b <- service2.liftD[Config] } yield a + b composed.exec(system)(TestInterpreter) composed.exec(system)(ProdInterpreter)


Слайд 31

Conclusion • Composability : Kleisli • Dependency Injection : Kleisli • Interpretation : Free monads • Fault Tolerance : Dependently typed checked exceptions


Слайд 32

Thanks! @raulraja @47deg http://github.com/47deg/func-architecture


Слайд 33


×

HTML:





Ссылка: