Se ha denunciado esta presentación.
Utilizamos tu perfil de LinkedIn y tus datos de actividad para personalizar los anuncios y mostrarte publicidad más relevante. Puedes cambiar tus preferencias de publicidad en cualquier momento.

Essence of the iterator pattern

The Essence of the Iterator pattern treats iterating over collections as two problems, which exhibit traversal of collections (and modifying the content) and accumulating values based on the contents. Jeremy Gibbons and Bruno C.d.S. Oliveira show how Applicative Functors and related type classes can be used in functional programming to solve these problems.

Paper: http://www.cs.ox.ac.uk/jeremy.gibbons/publications/iterator.pdf

The paper "Essence of the iterator pattern" is widely quoted amongst the functional programming community and illustrates nicely how recent acadamic research (Applicative Functors, McBride, 2008) finds its way into language design and application of functional programming languages such as Scala or Haskell.

The slides give a brief introduction and were presented at the "Papers We Love" Meetup in Hamburg.

  • Inicia sesión para ver los comentarios

Essence of the iterator pattern

  1. 1. Essence ofthe iteratorpatternMarkus Klink, @joheinz, markus.klink@inoio.de
  2. 2. Goal “Imperative iterations using the pattern have two simultaneous aspects: mapping and accumulating.” Jeremy Gibbons & Bruno C. d. S. Oliveira Markus Klink, @joheinz, markus.klink@inoio.de
  3. 3. Functionalmappingand accumulating trait Traverse[F[_]] extends Functor[F] with Foldable[F] { def traverse[G[_]: Applicative, A, B](fa: F[A])(f: A => G[B]): G[F[B]] } trait Applicative[F[_]] extends Functor[F] { def ap[A,B](fa: F[A])(f: F[A => B]) : F[B] def pure(a: A) : F[A] def map[A,B](fa: F[A])(f: A => B) : F[B] = ap(fa)(pure(f)) } Traverse takes a structure F[A], injects each element via the function f: A => G[B] into an Applicative G[_] and combines the results into G[F[B] using the applicative instance of G. Markus Klink, @joheinz, markus.klink@inoio.de
  4. 4. Acloser look trait Foldable[F[_]] { def foldRight[A, B](fa: F[A], z: => B)(f: (A, B) => B): B } trait Traverse[F[_]] extends Functor[F] with Foldable[F] { def traverse[G[_]: Applicative, A, B](fa: F[A])(f: A => G[B]): G[F[B]] } Look at the similarities! Markus Klink, @joheinz, markus.klink@inoio.de
  5. 5. Traversing is "almost" like Folding: » Look! No zero element: def foldRight[A, B](fa: F[A], z: => B)(f: (A, B) => B): B def traverse[G[_]: Applicative, A, B](fa: F[A])(f: A => G[B]): G[F[B]] » Look! B is wrapped in an Applicative and our F: def foldRight[A, B](fa: F[A], z: => B)(f: (A, B) => B): B def traverse[G[_]: Applicative, A, B](fa: F[A])(f: A => G[B]): G[F[B]] Markus Klink, @joheinz, markus.klink@inoio.de
  6. 6. Accumulating Markus Klink, @joheinz, markus.klink@inoio.de
  7. 7. val l = List(1,2,3) val result: Future[List[String]] = l.traverse(a => Future.successful { a.toString }) How? // traverse implementation of List, G[_] is the future override def traverse[G[_]: Applicative, A, B](fa: List[A])(f: (A) => G[B]): G[List[B]] = { val emptyListInG: G[List[B]] = Applicative[G].pure(List.empty[B]) // if the list is empty we need a Future { List.empty() } // f(a) gives us another G[B], which we can inject into B => B => Future[List[B]] foldRight(fa, emptyListInG) { (a: A, fbs: G[List[B]]) => Applicative[G].apply2(f(a), fbs)(_ +: _) } } // applicative instance of Future, example... override def ap[A,B](F: => Future[A])(f: => Future[A => B]) : Future[B] = { for { a <- F g <- f } yield { g(a) } } Markus Klink, @joheinz, markus.klink@inoio.de
  8. 8. Gibbons & Oliveiraclaimthatwe can do: val x : List[Char]= "1234".toList val result : Int = x.traverse(c => ???) assert(4 == result) The claim is that we can accumulate values, or write the length function just with Traverse/Applicative Markus Klink, @joheinz, markus.klink@inoio.de
  9. 9. How? » we need a result type G[List[Int]] which equals Int » G needs to be an applicative » we need to calculate a sum of all the values. » we need a zero value in case the list is empty, because of ... val emptyListInG: G[List[B]] = Applicative[G].pure(List.empty[B]) Markus Klink, @joheinz, markus.klink@inoio.de
  10. 10. Each Monoid gives risetoan applicative trait Monoid[F] extends Semigroup[F] { self => /** * the identity element. */ def zero: F def applicative: Applicative[Lambda[a => F]] = new Applicative[Lambda[a => F]] { // mapping just returns ourselves override def map[A, B](fa: F)(f: A => B) : F = fa // putting any value into this Applicative will put the Monoid.zero in it def pure[A](a: => A): F = self.zero // Applying this Applicative combines each value with the append function. def ap[A, B](fa: => F)(f: => F): F = self.append(f, fa) } Markus Klink, @joheinz, markus.klink@inoio.de
  11. 11. How2 Part of the trick is this type: Applicative[Lambda[a => F]]! It means that we throw everything away and create a type G[_] which behaves like F. So... val x : List[Char]= "1234".toList val charCounter : Applicative[Lambda[a => Int]] = Monoid[Int].applicative // we just "reversed" the parameters of traverse // the summing is done automatically via append charCounter.traverse(x)(_ => 1) Markus Klink, @joheinz, markus.klink@inoio.de
  12. 12. Counting lines In the previous example we assigned each char in the list to a 1 and let the Monoid do the work. val x : List[Char] = "1233n1234n" val lineCounter : Applicative[Lambda[a => Int]] = Monoid[Int].applicative lineCounter.traverse(x){c => if (c == 'n') 1 else 0 } Markus Klink, @joheinz, markus.klink@inoio.de
  13. 13. Products ofApplicatives Doing bothatthe sametimewithinasingle traversal val x : List[Char]= "1234n1234n" val counter : Applicative[Lambda[a => Int]] = Monoid[Int].applicative val charLineApp : Applicative[Lambda[a => (Int, Int)]] = counter.product[Lambda[a => Int](counter) val (chars, lines) = counter.traverse(x){c => (1 -> if (c == 'n') 1 else 0 } Markus Klink, @joheinz, markus.klink@inoio.de
  14. 14. Countingwords Counting words cannot work on the current position alone. We need to track changes from spaces to non spaces to recognize the beginning of a new word. def atWordStart(c: Char): State[Boolean, Int] = State { (prev: Boolean) => val cur = c != ' ' (cur, if (!prev && cur) 1 else 0) } val WordCount : Applicative[Lambda[a => Int]] = State.stateMonad[Boolean].compose[Lambda[a => Int](counter) val StateWordCount = WordCount.traverse(text)(c => atWordStart(c)) StateWordCount.eval(false) Markus Klink, @joheinz, markus.klink@inoio.de
  15. 15. Usingthe productofall3 countersto implementwc val AppCharLinesWord: Applicative[Lambda[a => ((Int, Int), State[Boolean, Int])]] = Count // char count .product[Lambda[a => Int]](Count) // line count .product[Lambda[a => State[Boolean, Int]]](WordCount) // word count val ((charCount, lineCount), wordCountState) = AppCharLinesWord.traverse(text)((c: Char) => ((1, if (c == 'n') 1 else 0), atWordStart(c))) val wordCount: Int = wordCountState.eval(false) Markus Klink, @joheinz, markus.klink@inoio.de
  16. 16. Collecting some state and modifying elements Markus Klink, @joheinz, markus.klink@inoio.de
  17. 17. // start value public Integer count = 0; public Collection<Person> collection = ...; for (e <- collection) { // or we use an if count = count + 1; // side effecting function // or we map into another collection e.modify(); } Obviously in a functional programming language, we would not want to modify the collection but get back a new collection. We also would like to get the (stateful) counter back. Markus Klink, @joheinz, markus.klink@inoio.de
  18. 18. def collect[G[_]: Applicative, A, B](fa: F[A])(f: A => G[Unit])(g: A => B): G[F[B]] = { val G = implicitly[Applicative[G]] val applicationFn : A => G[B] = a => G.ap(f(a))(G.pure((u: Unit) => g(a))) self.traverse(fa)(applicationFn) } def collectS[S, A, B](fa: F[A])(f: A => State[S, Unit])(g: A => B): State[S, F[B]] = { collect[Lambda[a => State[S, a]], A, B](fa)(f)(g) } val list : List[Person] = ... val stateMonad = State.stateMonad[Int] val (counter, updatedList) = list.collectS{ a => for { count <- get; _ <- put(count + 1) } yield ()}(p => p.modify()).run(0) Markus Klink, @joheinz, markus.klink@inoio.de
  19. 19. Modifying elements depending on some state Markus Klink, @joheinz, markus.klink@inoio.de
  20. 20. // start value public Collection<Person> collection = ...; for (e <- collection) { e.modify(stateful()); } Now the modification depends on some state we are collecting. Markus Klink, @joheinz, markus.klink@inoio.de
  21. 21. def disperse[G[_]: Applicative, A, B, C](fa: F[A])(fb: G[B], g: A => B => C): G[F[C]] = { val G = implicitly[Applicative[G]] val applicationFn: A => G[C] = a => G.ap(fb)(G.pure(g(a))) self.traverse(fa)(applicationFn) } def disperseS[S, A, C](fa: F[A])(fb: State[S, S], g: A => S => C) : State[S,F[C]] = { disperse[Lambda[a => State[S, a]], A, S, C](fa)(fb, g) } Markus Klink, @joheinz, markus.klink@inoio.de
  22. 22. THANKS Markus Klink, @joheinz, markus.klink@inoio.de
  23. 23. Some resources http://etorreborre.blogspot.de/2011/06/essence-of- iterator-pattern.html Markus Klink, @joheinz, markus.klink@inoio.de

×