O futuro chegou: Programação concorrente com futures LEONARDO BORGES SENIOR CLOJURE ENGINEER @LEONARDO_BORGES
SOBRE Um pouco sobre mim Senior Clojure Engineer na Atlassian, Sydney Fundador do Grupo de Usuários Clojure de Sydney Autor de Clojure Reactive Programming - http://bit.ly/cljrp * QCon discount code: CRP10
O quê? FUTURES CONCORRÊNCIA ABSTRAÇÃO COMPOSIÇÃO
Futures em Java <= 1.7
FUTURES EM JAVA <= 1.7 static ExecutorService es = Executors.newCachedThreadPool(); static Integer doubler(integer n) { return 2 * n; } static Future<Integer> servicea(integer n) { return es.submit(() -> { Thread.sleep(1000); return n; }); } static Future<Integer> serviceb(integer n) { return es.submit(() -> { Thread.sleep(1500); return Double.valueOf(Math.pow(n, 2)).intValue(); }); } static Future<Integer> servicec(integer n) { return es.submit(() -> { Thread.sleep(2000); return Double.valueOf(Math.pow(n, 3)).intValue(); }); }
FUTURES EM JAVA <= 1.7 Bloqueia a thread Integer doubled = doubler(servicea(10).get()); System.out.println("Couldn't do anything else while the line above was being executed..."); System.out.println("Result: " + serviceb(doubled).get() + " - " + servicec(doubled).get()); Bloqueia a thread Bloqueia a thread
Problemas Desperdício de processamento
Problemas Desperdício de processamento Baixo nível de composição
E no Java 8?
FUTURES NO JAVA 8 final CompletableFuture<Integer> doubled = servicea(10).thenapply(completablefutures::doubler); final CompletableFuture<Integer> resultb = doubled.thencompose(completablefutures::serviceb); final CompletableFuture<Integer> resultc = doubled.thencompose(completablefutures::servicec); CompletableFuture<Void> allfutures = CompletableFuture.allOf(resultB, resultc); allfutures.whencomplete((v, ex) -> { try { System.out.println("Result: " + resultb.get() + " - " + resultc.get()); } catch (Exception e) {} }); System.out.println("Doing other important things...");
FUTURES NO JAVA 8 final CompletableFuture<Integer> doubled = servicea(10).thenapply(completablefutures::doubler); final CompletableFuture<Integer> resultb = doubled.thencompose(completablefutures::serviceb); final CompletableFuture<Integer> resultc = doubled.thencompose(completablefutures::servicec); CompletableFuture<Void> allfutures = CompletableFuture.allOf(resultB, resultc); allfutures.whencomplete((v, ex) -> { try { System.out.println("Result: " + resultb.get() + " - " + resultc.get()); } catch (Exception e) {} }); System.out.println("Doing other important things..."); Não bloqueia a thread
Esses combinadores são familiares?
STREAMS NO JAVA 8 List<Integer> ns = Arrays.asList(1, 2, 3, 4); Function<Integer, Integer> doubler = (i) -> i * 2; System.out.println(ns.stream().map(doubler).collect(Collectors.toList())); // [2, 4, 6, 8] Function<Integer, Stream<? extends Integer>> torange = (i) -> IntStream.range(0, i).boxed(); Stream<Integer> combined = ns.stream().map(doubler).flatmap(torange); System.out.println(combined.collect(Collectors.toList())); // [0, 1, 0, 1, 2, 3, 0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5, 6, 7]
Streams vs Futures Stream<R> map(function<? super T,? extends R> mapper) { } Stream<R> flatmap(function<? super T,? extends Stream<? extends R>> mapper) { } CompletableFuture<U> thenapply(function<? super T,? extends U> fn) { } CompletableFuture<U> thencompose(function<? super T,? extends CompletionStage<U>> fn) { }
Streams vs Futures Stream<R> map(function<? super T,? extends R> mapper) { } Stream<R> flatmap(function<? super T,? extends Stream<? extends R>> mapper) { } CompletableFuture<U> thenapply (Function<? super T,? extends U> fn) { } CompletableFuture<U> thencompose(function<? super T,? extends CompletionStage<U>> fn) { }
E se quisermos escrever funções que funcionem com Streams e Futures?
SEQUENCING FUTURES CompletableFuture<Collection<Integer>> result = // java.util.concurrent.completablefuture[10, 100, 1000] sequence(servicea(10), serviceb(10), servicec(10));
SEQUENCING FUTURES static <A> CompletableFuture<Collection<A>> sequence(completablefuture<a>... cfs) { return Arrays.asList(cfs).stream().reduce( CompletableFuture.completedFuture(new ArrayList<>()), (acc, future) -> acc.thencompose((xs) -> future.thenapply((x) -> { xs.add(x); return xs; })), (a, b) -> a.thencompose((xs) -> b.thenapply((ys) -> { xs.addall(ys); return xs; }))); }
SEQUENCING FUTURES static <A> CompletableFuture<Collection<A>> sequence(completablefuture<a>... cfs) { return Arrays.asList(cfs).stream().reduce( CompletableFuture.completedFuture(new ArrayList<>()), (acc, future) -> acc.thencompose((xs) -> future.thenapply((x) -> { xs.add(x); return xs; })), (a, b) -> a.thencompose((xs) -> b.thenapply((ys) -> { xs.addall(ys); return xs; }))); }
SEQUENCING FUTURES static <A> CompletableFuture<Collection<A>> sequence(completablefuture<a>... cfs) { return Arrays.asList(cfs).stream().reduce( CompletableFuture.completedFuture(new ArrayList<>()), (acc, future) -> acc.thencompose((xs) -> future.thenapply((x) -> { xs.add(x); return xs; })), (a, b) -> a.thencompose((xs) -> b.thenapply((ys) -> { xs.addall(ys); return xs; }))); }
SEQUENCING FUTURES static <A> CompletableFuture<Collection<A>> sequence(completablefuture<a>... cfs) { return Arrays.asList(cfs).stream().reduce( CompletableFuture.completedFuture(new ArrayList<>()), (acc, future) -> acc.thencompose((xs) -> future.thenapply((x) -> { xs.add(x); return xs; })), (a, b) -> a.thencompose((xs) -> b.thenapply((ys) -> { xs.addall(ys); return xs; }))); }
SEQUENCING FUTURES static <A> CompletableFuture<Collection<A>> sequence(completablefuture<a>... cfs) { return Arrays.asList(cfs).stream().reduce( CompletableFuture.completedFuture(new ArrayList<>()), (acc, future) -> acc.thencompose((xs) -> future.thenapply((x) -> { xs.add(x); return xs; })), (a, b) -> a.thencompose((xs) -> b.thenapply((ys) -> { xs.addall(ys); return xs; }))); }
SEQUENCING STREAMS Stream<Integer> s1 = Arrays.asList(1).stream(); Stream<Integer> s2 = Arrays.asList(2).stream(); Stream<Integer> s3 = Arrays.asList(3).stream(); sequences(s1, s2, s3) // [[1, 2, 3]]
SEQUENCING STREAMS static <A> Stream<Stream<A>> sequences(stream<a>... cfs) { return Arrays.asList(cfs).stream().reduce( Stream.of(Stream.empty()), (acc, coll) -> acc.flatmap((xs) -> coll.map((x) -> Stream.concat(xs, Stream.of(x)))), (acc, coll) -> acc.flatmap((xs) -> coll.map((x) -> Stream.concat(xs, x)))); }
SEQUENCING STREAMS static <A> Stream<Stream<A>> sequences(stream<a>... cfs) { return Arrays.asList(cfs).stream().reduce( Stream.of(Stream.empty()), (acc, coll) -> acc.flatmap((xs) -> coll.map((x) -> Stream.concat(xs, Stream.of(x)))), (acc, coll) -> acc.flatmap((xs) -> coll.map((x) -> Stream.concat(xs, x)))); }
SEQUENCING STREAMS static <A> Stream<Stream<A>> sequences(stream<a>... cfs) { return Arrays.asList(cfs).stream().reduce( Stream.of(Stream.empty()), (acc, coll) -> acc.flatmap((xs) -> coll.map((x) -> Stream.concat(xs, Stream.of(x)))), (acc, coll) -> acc.flatmap((xs) -> coll.map((x) -> Stream.concat(xs, x)))); }
SEQUENCING STREAMS static <A> Stream<Stream<A>> sequences(stream<a>... cfs) { return Arrays.asList(cfs).stream().reduce( Stream.of(Stream.empty()), (acc, coll) -> acc.flatmap((xs) -> coll.map((x) -> Stream.concat(xs, Stream.of(x)))), (acc, coll) -> acc.flatmap((xs) -> coll.map((x) -> Stream.concat(xs, x)))); }
Perceberam alguma semelhança?
FUTURES VS STREAMS static <A> CompletableFuture<Collection<A>> sequence(completablefuture<a>... cfs) { return Arrays.asList(cfs).stream().reduce( CompletableFuture.completedFuture(new ArrayList<>()), (acc, future) -> acc.thencompose((xs) -> future.thenapply((x) -> { xs.add(x); return xs; })), (a, b) -> a.thencompose((xs) -> b.thenapply((ys) -> { xs.addall(ys); return xs; }))); } static <A> Stream<Stream<A>> sequences(stream<a>... cfs) { return Arrays.asList(cfs).stream().reduce( Stream.of(Stream.empty()), (acc, coll) -> acc.flatmap((xs) -> coll.map((x) -> Stream.concat(xs, Stream.of(x)))), (acc, coll) -> acc.flatmap((xs) -> coll.map((x) -> Stream.concat(xs, x)))); }
FUTURES VS STREAMS static <A> CompletableFuture<Collection<A>> sequence(completablefuture<a>... cfs) { return Arrays.asList(cfs).stream().reduce( CompletableFuture.completedFuture(new ArrayList<>()), (acc, future) -> acc.thencompose((xs) -> future.thenapply((x) -> { xs.add(x); return xs; })), (a, b) -> a.thencompose((xs) -> b.thenapply((ys) -> { xs.addall(ys); return xs; }))); } static <A> Stream<Stream<A>> sequences(stream<a>... cfs) { return Arrays.asList(cfs).stream().reduce( Stream.of(Stream.empty()), (acc, coll) -> acc.flatmap((xs) -> coll.map((x) -> Stream.concat(xs, Stream.of(x)))), (acc, coll) -> acc.flatmap((xs) -> coll.map((x) -> Stream.concat(xs, x)))); }
FlatMappable
FLATMAPPABLE <M extends FlatMappable, A> M<List<A>> sequence(m<a>... ma) { }
Chegando no limite do sistema de tipos Java não suporta tipos de alta espécie (higher kinded types) Tipos de alta espécie são indispensáveis ao se implementar tais abstrações
Colocando nome nos bois
FlatMappable se chama Monad trait Monad[F[_]] { def point[a](a: => A): F[A] def bind[a, B](a: F[A])(f: A => F[B]): F[B] def map[a, B](a: F[A])(f: A => B): F[B] = bind(a)(b => point(f(b))) }
FlatMappable se chama Monad trait Monad[F[_]] { Tipos de alta espécie em ação def point[a](a: => A): F[A] def bind[a, B](a: F[A])(f: A => F[B]): F[B] def map[a, B](a: F[A])(f: A => B): F[B] = bind(a)(b => point(f(b))) }
MONADS EM SCALA O Monad de Futures implicit def FutureMonad: Monad[Future] = new Monad[Future] { def point[a](a: => A) = Future.successful(a) } def bind[a, B](a: Future[A])(f: A => Future[B]): Future[B] = a flatmap f
MONADS EM SCALA O Monad de Listas implicit def ListMonad: Monad[List] = new Monad[List] { def point[a](a: => A) = List(a) def bind[a, B](a: List[A])(f: A => List[B]): List[B] = a flatmap f }
MONADS EM SCALA Implementando sequence def sequence[m[_] : Monad, A](ma: List[M[A]]): M[List[A]] = { ma.foldleft(monad[m].point(list(): List[A]))((acc, m) => acc.flatmap((xs) => m.map((x) => xs :+ x)) ) }
Being abstract is something profoundly different from being vague The purpose of abstraction is not to be vague, but to create a new semantic level in which one can be absolutely precise. EDSGER W. DIJKSTRA
MONADS EM SCALA Sequencing val resultf: Future[List[Integer]] = sequence(list(servicea(10), serviceb(10), servicec(10))) println(await.result(resultf, Duration(2, "seconds"))) // List(10, 100, 1000) val resultl: List[List[Int]] = sequence(list(list(1,2,3), List(4,5,6), List(7,8,9))) println(resultl) // List(List(1, 4, 7), List(2, 4, 7), List(3, 4, 7), List(1, 5, 7),...)
Demais! O quê mais podemos fazer??
Folding
FOLDING List(2, 3, 4).reduce(_+_) //9
FOLDING List(2, 3, 4).reduce(_+_) //9 val intfutures = List(Future.successful(1), Future.successful(2), Future.successful(3)) val result: Future[Int] = sequence(intfurures).map((x) => x.reduce(_ + _)) // Future[9]
Existe algo em comum?
Introduzindo Foldable trait Foldable[F[_]] { self => def fold[m: Monoid](t: F[M]): M =??? }
Introduzindo Monoids trait Monoid[F] { self => def zero: F def append(f1: F, f2: => F): F }
Introduzindo Monoids: Ints implicit def intmonoid: Monoid[Int] = new Monoid[Int] { def zero: Int = 0 def append(f1: Int, f2: => Int): Int = f1 + f2 }
Introduzindo Monoids: Ints implicit def intmonoid: Monoid[Int] = new Monoid[Int] { def zero: Int = 0 def append(f1: Int, f2: => Int): Int = f1 + f2 } Foldable[List].fold(List(2, 3, 4))) //9
Introduzindo Monoids: Futures
Introduzindo Monoids: Futures implicit def futurefreemonoid[a] = new Monoid[Future[List[A]]] { def zero: Future[List[A]] = Future.successful(List()) def append(f1: Future[List[A]], f2: => Future[List[A]]) = for { a1 <- f1 a2 <- f2 } yield a1 ++ a2 }
Introduzindo Monoids: Futures implicit def futurefreemonoid[a] = new Monoid[Future[List[A]]] { def zero: Future[List[A]] = Future.successful(List()) def append(f1: Future[List[A]], f2: => Future[List[A]]) = for { a1 <- f1 a2 <- f2 } yield a1 ++ a2 } Foldable[List].fold(List(Future.successful(2), Future.successful(3), Future.successful(4))) // Future[9]
Monad, Foldable e Monoid são apenas o começo
Em Scala, muitas delas já foram implementadas em Scalaz
A Teoria das Categorias pode ter um impacto grande na criação de bibliotecas
Being abstract is something profoundly different from being vague The purpose of abstraction is not to be vague, but to create a new semantic level in which one can be absolutely precise. EDSGER W. DIJKSTRA
Referências Clojure Reactive Programming - http://bit.ly/cljrp Java 8 CompletableFuture - http://bit.ly/j8future Java 8 Streams - http://bit.ly/j8stream Category Theory - http://amzn.to/1nfl08u Free Monoids - http://en.wikipedia.org/wiki/free_monoid Scalaz - https://github.com/scalaz/scalaz Fluokitten (Clojure) - https://github.com/uncomplicate/fluokitten
Obrigado! Q&A LEONARDO BORGES SENIOR CLOJURE DEVELOPER @LEONARDO_BORGES
We are hiring! LEONARDO BORGES SENIOR CLOJURE DEVELOPER @LEONARDO_BORGES