Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Using scala Futures the functional way

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.

Using scala Futures the functional way

Get more code reuse from your Futures and add concurrency to your programs in a functional way. Hint: a word that beings with 'M'...

Avatar for Vincent Marquez

Vincent Marquez

January 22, 2015
Tweet

More Decks by Vincent Marquez

Other Decks in Programming

Transcript

  1. I write code for 
 ! www.JazelAuto.com 
 (automotive advertising

    platform) 
 ! www.10thdegree.com
 (digital marketing agency)
 
 We consume and process a lot of data. 
 Necessitates concurrency, specifically Futures.
  2. We need a flexible way to sprinkle in concurrency that

    doesn’t affect our program logic. We need to utilize concurrency(Futures) with all sorts of different data structures.
  3. ! def futureBasics(): Unit = {! val f1 = Future

    { 1 + 2 } //happening asynchronously! val f2 = Future { 3 + 5 } ! ! val f3 = for {! a <- f1! b <- f2! } yield ! a + b! ! //we only want to Await at the end of the world ! val result = Await.result(f3, Duration(5, SECONDS))! ! println(result)! }!
  4. ! //What if we want parallelism but for a function

    returning option?! def doStuff[A](a1: A, a2: A)(f: A => Option[Int]): Future[Option[Int]] = {! val fi1 = Future { f(a1) }! val fi2 = Future { f(a2) }! for {! o1 <- fi1 //call to map! o2 <- fi2! } yield! for {! i1 <- o1! i2 <- o2! } yield! i1 + i2! }!
  5. This is very common, we need to work with Futures

    over some other context (Option/Either/Kleisli(Reader) etc.) To solve that problem we need a detour on abstraction…
  6. //Can we generalize this?! ! val o1 = Some(2) //

    or 1.some! ! val l1 = List(1,2,3)! ! val f1 = Future { 1+6 }! ! ! val r1 = o1.map(i => i*i)! ! val r2 = l1.map(i => i*i)! ! val r3 = f1.map(i => i*i)!
  7. //can we abstract over that idea? Subclass polymorphism?! def squareInContext(s:

    Seq[Int]): Seq[Int] = ! s.map(i => i*i)! ! //No, not quite right. ! ! def squareInContextF[A <: Seq[Int]](s: A)! ! (implicit ev: Seq[Int] =:= A): A = ! ! s.map(i => i*i)! ! //Ugly, and doesn’t work with things that don’t extend Seq!
  8. What if we could abstract over anything that had a

    ‘map like’! method, as long as we provided evidence that the type could be ‘mapped’?
  9. //F[_] is called a higher kinded type! trait Mapable[F[_]] {!

    def map[A,B](fa: F[A])(mf: A => B): F[B]! }! ! implicit def listMapable: Mapable[List] = new Mapable[List] {! def map[A,B](fa: List[A])(mf: A => B): List[B] = fa.map(mf)! }! ! implicit def futureMapable: Mapable[Future] = new Mapable[Future] {! def map[A,B](fa: Future[A])(mf: A => B): Future[B] = fa.map(mf)! }!
  10. //Function0[A] is the type of () => A! implicit def

    functionMappable: Mapable[Function0] = ! ! new Mapable[Function0] {! def map[A,B](f: Function0[A])(mf: A => B): Function0[B] = ! ! ! ! () => mf(f())! } !
  11. ! ! What do we gain? Considering we can call

    a method in the map, not much. Yet…
  12. ! trait FlatMapable[F[_]] extends Mapable[F] {! ! //notice => A,

    not A. ! def point[A](a: => A): F[A]! ! def flatMap[A,B](fa: F[A])(fm: A => F[B]): F[B]! ! //we can derive map from the previous two methods! ! override def map[A,B](fa: F[A])(m: A => B): F[B] = ! ! ! flatMap(fa)(a => point(m(a)))! }! !
  13. implicit def optionFlatMapable: FlatMapable[Option] = ! ! new FlatMapable[Option] {!

    def point[A](a: => A): Option[A] = Option(a)! ! ! def flatMap[A,B](fa: Option[A])(f: A => Option[B]): Option[B] = ! ! ! fa.flatMap(a => ! f(a)) ! }! ! implicit def futureFlatMapable: FlatMapable[Future] = ! ! new FlatMapable[Future] {! !def point[A](a: => A): Future[A] = Future { a }! ! ! ! def flatMap[A,B](fa: Future[A])(f: A => Future[B]): Future[B] = ! ! ! ! fa.flatMap(a => f(a))! }! !
  14. ! object UnsafeJavaWebApi { ! def unsafeApi(i: Int): String =

    {! if (i % 2 == 0)! null! else {! //long computation might occur here! i+5.toString! }! }! ! def unsafeApi2(s: String): String = ! try {! (s.toInt+1).toString! } catch {! case ex: Exception => null ! }! }!
  15. // Let’s make use of our flatMapable trait. We’ll create

    a pipeline ! def pipeline[F[_]](fa: F[Int])(implicit M: FlatMapable[F]): F[String] = {! val newm = M.point(unsafeApi(2))! M.flatMap(fa)(i => ! M.flatMap(M.point(unsafeApi(i)))(s => ! M.flatMap(M.point(unsafeApi2(s+"blah")))(s2 => ! M.map(newm)(s3 => s3 + s2)! )! )! )! }!
  16. ! So why all this trouble? Why abstract over the

    flatMapable? I thought this was a talk on Futures?!
  17. //we get short circuiting. if null is passed to point,

    we return none! def doPipeLine(i: Int): Option[String] = ! pipeline(Option(i))! ! ! // we get parallelism (because of the call to point in the pipeline)! def doPipelineF(i: Int): Future[String] = ! pipeline(Future { i })!
  18. ! What if we want both short circuiting and parallelism?

    We could make the method take an F[G[A]] and require FlatMapable’s for both kind parameters, but it’s not scalable. Remember the nested ‘fors’?
  19. ! ! class OptionHelper[F[_],A](val foa: F[Option[A]]) {! ! ! def

    flatMap[B](f: A => OptionHelper[F,B])! ! ! ! ! ! ! (implicit M:FlatMapable[F]): OptionHelper[F,B] = ! ! ! ! ! ! new OptionHelper[F,B](M.flatMap(foa)(o => o match {! case Some(a) => f(a).foa! case None => M.point(None: Option[B])! }))! }! ! ! /*! we are able to access what’s inside the F[Option[A]] no matter what the F is now, as long as there exists an implicit FlatMapable for F!! */ !
  20. ! Ok now we want a ‘FlatMapable’ trait for OptionHelper.

    But there’s a problem. The type parameter for FlatMapable needs to have a single kind parameter. 
 
 The types for OptionHelper are [_],A. [_],A doesn’t fit in something that takes just an [_].! !
  21. ! //Option 1: Type Aliasing. (The one your coworkers will

    prefer).??! ! type OptionFutureHelper[A] = OptionHelper[Future,A]! ! def OptionHelperFutureFlatMapable(implicit M: FlatMapable[Future]): ! ! FlatMapable[OptionFutureHelper] = ! ! new FlatMapable[OptionFutureHelper] {! def point[A](a: => A): OptionFutureHelper[A] =! ! ! new OptionHelper(M.point(Option(a)))! ! def flatMap[A,B](fa: OptionFutureHelper[A])! ! ! (f: A => OptionFutureHelper[B]): OptionFutureHelper[B] =! ! ! ! fa.flatMap(f)(M)! ! }!
  22. ! //Option 2: no type alias necessary…but uglier and you

    will have to explain this to all your new team members.! ! implicit def OptionHelperFlatMapable[F[_]](implicit M: FlatMapable[F]): ! ! FlatMapable[({ type l[a] = OptionHelper[F,a]})#l] = ! ! new FlatMapable[({ type l[a] = OptionHelper[F,a]})#l] { ! ! ! def point[A](a: => A): OptionHelper[F,A] = ! ! ! ! new OptionHelper(M.point(Option(a)))! ! !! def flatMap[A,B](fa: OptionHelper[F,A])(f: A => OptionHelper[F,B]):! ! ! ! OptionHelper[F,B] = fa.flatMap(f)! }!
  23. ! ! FlatMapable[({ type l[a] = OptionHelper[F,a]})#l]! ! ! All

    we are doing is declaring ‘on the fly’ a type alias called L that takes a single parameter (a), that is equal to the OptionHelper[F,a]. The F comes from the class declaration.
  24. //NOW WE HAVE BOTH! ! ! def runPipelineOptionHelper(): Unit =

    {! val oh: OptionFutureHelper[Int] = ! ! ! new OptionHelper[Future,Int](Future { Some(1) })! pipeline(oh) ! }! ! // We get BOTH parallelism and we short circuit on any None value. !
  25. class FlatMapableOps[F[_],A](fa: F[A])(implicit M: FlatMapable[F]) {! def flatMap[B](f: A =>

    F[B]): F[B] = M.flatMap(fa)(f)! def map[B](f: A => B): F[B] = M.map(fa)(f)! }! ! implicit def toFlatMapableOps[F[_],A](fa: F[A])! ! (implicit M: ! FlatMapable[F]) = new FlatMapableOps(fa)! ! ! //remember, anything with flatMap and Map methods get nice syntactic sugar from scala ala for comprehension. !
  26. //Ugly version one more time! ! def pipeline[F[_]](fa: F[Int])(implicit M:

    FlatMapable[F]): F[String] = {! val newm = M.point(unsafeApi(2))! M.flatMap(fa)(i => ! M.flatMap(M.point(unsafeApi(i)))(s => ! M.flatMap(M.point(unsafeApi2(s+"blah")))(s2 => ! M.map(newm)(s3 => s3 + s2)! )! )! )! }!
  27. //Rewritten pipeline method:! ! def pipeline2[F[_]](fa: F[Int])(implicit M: FlatMapable[F]): F[String]

    = {! val nm = M.point(unsafeApi(2))! for {! i <- fa! s <- M.point(unsafeApi(i))! s2 <- M.point(unsafeApi2(s+"blah"))! s3 <- nm! } yield! s3 + s2 ! }! 

  28. We are writing code that abstracts away everything but the

    necessary methods. We still exploit the benefit of whatever F is passed in. We can nest multiple F’s to get multiple benefits just as we did with our OptionHelper with a Future.
  29. Monad! Along with defining the two aforementioned type classes, we

    get a plethora of instances of them for free too. Future, Option, List, Stream, Tuple, Either,\/ (right biased Either), Nel. ! ! OptionHelper is already written too, it is called OptionT (because it is the Option Monad Transformer). ! ! Mapable (Functor) and FlatMapable (Monad) are not the only ways to abstract over things! 
 

  30. //Exactly the same but with different type class names! def

    pipeline3[F[_]](fa: F[Int])(implicit M: Monad[F]): F[String] = {! val nm = M.point(unsafeApi(2))! for {! i <- fa! s <- M.point(unsafeApi(i))! s2 <- M.point(unsafeApi2(s+"blah"))! s3 <- nm! } yield! s3 + s2 ! }!
  31. ! //need the type lambda to help out scalac! def

    doPipelineScalaz(i: Int): OptionT[Future,String] = {! val ot= OptionT( Future { 1.some } )! pipeline3[({ type l[a] = OptionT[Future,a]})#l](ot)! }!
  32. //\/.fromTryCatchNonFatal just wraps an expression in \/[Exception, A]! ! def

    tryUnsafeApi(i: Int): \/[String, Int] = ! ! \/.fromTryCatchNonFatal( i + 2).leftMap(ex => ex.toString) ! ! def tryUnsafeApi2(s: String): \/[String, String] = ! \/.fromTryCatchNonFatal(s + "asdf" ).leftMap(ex => ex.toString)!
  33. def pipeline4[F[_]](fa: EitherT[F, String, Int])! ! ! ! ! !

    ! ! (implicit M: Monad[F]): EitherT[F, String, String] = {! val nm = EitherT(M.point(tryUnsafeApi(2)))! for {! i <- fa! s <- EitherT(M.point(tryUnsafeApi(i)))! s2 <- EitherT(M.point(tryUnsafeApi2(s + "blah")))! s3 <- nm! } yield ! s3 + s2! }! ! /*We can now short circuit getting an error message, and abstract over whatever F is (Future for parallelism for instance).*/!
  34. //Run every element in the list through the pipeline, separate

    right vs. left! ! def transformPipeline[F[_], A](l: List[Int], f: EitherT[F, String, Int] => EitherT[F, String, A])! ! (implicit M: Monad[F]): F[(List[String], List[A])] = ???!
  35. def separateSequence[F[_], A, B](list: List[EitherT[F,A,B]]) (implicit M: Monad[F]): F[(List[A],List[B])] =

    {! list.foldRight(M.point((List[A](), List[B]())))( (et, ftup) => {! for {! e <- et.run! t <- ftup! } yield e.fold(a => (a :: t._1,t._2), b => (t._1, b :: t._2)) ! })! }!
  36. ! def pipelineList[F[_]](l: List[Int])! ! (implicit M: Monad[F]): F[(List[String], List[String])]

    = {! ! //List[EitherT[Future,String,int]] ! ! val eithers = l.map(i => ! ! ! Monad[({ type l[a] = EitherT[F, String, a]})#l].point(i)) ! val pipelineResults = eithers.map(et => pipeline4(et))! separateSequence(pipelineResults)! }!
  37. This is just the beginning. We could actually abstract away

    the List to a Foldable type class so it wouldn’t matter if we had a List, Stream, DList, NonEmptyList, IList, FingerTree, etc
  38. Any time you’re forced to sequence expressions with a flatMap,

    you need ordering. If not, there is potential for concurrency. The more you abstract functions, often times the simpler it is to spot potential for concurrency.