Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Traversals for all ocasions


Published on

Scalar 2018

Published in: Software
  • Hello! Get Your Professional Job-Winning Resume Here - Check our website!
    Are you sure you want to  Yes  No
    Your message goes here

Traversals for all ocasions

  2. 2. Software Developer at codecentric Co-organizer of ScalaDus and IdrisDus Maintainer of cats, cats-effect, cats-mtl, OutWatch Enthusiastic about FP About me
  3. 3. Motivation ● Traversable is my favorite type class ● Not enough people know about it ● Even less people know about some of the less common traversals out there ● Have some fun while learning!
  4. 4. Motivation
  5. 5. Teaser How do you create a List of Http Requests run them all in parallel and if errors occur accumulate them? userIdList.parTraverse(request).value
  6. 6. Traverse [T[_]: Traverse, F[_]: Applicative, A]: T[F[A]] => F[T[A]] E.g. List[Future[A]] => Future[List[A]] Vector[Option[A]] => Option[Vector[A]]
  7. 7. Traverse [T[_]: Traverse, F[_]: Applicative, A]: T[F[A]] => F[T[A]] Traverse runs an action(F[_]) for every element in a data structure (T[_]), and accumulates the results.
  8. 8. Traverse trait Applicative[F[_]] { def map2[A, B, C](fa: F[A], fb: F[B])(f: (A, B) => C): F[C] def pure[A](a: A): F[A] } Applicatives allow us to combine two or more independent values inside a context.
  9. 9. Traverse trait Traverse[T[_]] { def sequence[F[_]: Applicative, A](tfa: T[F[A]]): F[T[A]] def traverse[F[_]: Applicative, A, B](ta: T[A], f: A => F[B]): F[T[B]] } t.traverse(f) <->
  10. 10. Traverse List[Future[A]] => Future[List[A]] Vector[Option[A]] => Option[Vector[A]] Either[E, IO[A]] => IO[Either[E, A]] type EitherE[A] = Either[E, A] List[Validated[E, A]] => Validated[E, List[A]]
  11. 11. FoldMap trait Foldable[T[_]] { def foldMap[A, M: Monoid](ta: T[A], f: A => M): M def fold[M: Monoid](ta: T[M]): M } t.foldMap(f) <-> def foldMap[A, M: Monoid](ta: T[A], f: A => M): M = ta.traverse(a => Const(f(a))).getConst
  12. 12. NonEmpty trait Reducible[T[_]] { def reduceMap[A, S: Semigroup](ta: T[A], f: A => S): S def reduce[S: Semigroup](ta: T[S]): S } trait NonEmptyTraverse[T[_]] { def nonEmptyTraverse[F[_]: Apply](ta: T[A], f: A => F[B]): F[T[B]] def nonEmptySequence[F[_]: Apply](ta: T[F[A]]): F[T[A]] } t.reduceMap(f) <-> t.nonEmptyTraverse(f) <->
  13. 13. NonEmpty def maximum[A: Order](nel: NonEmptyList[A]): A = reduceMap(nel)(Max) def minimum[A: Order](nel: NonEmptyList[A]): A = reduceMap(nel)(Min)
  14. 14. NonEmpty def countWords(text: String): Map[String, Int] = words.split(" ").groupBy(identity).mapValues(_.length) val lines: NonEmptyList[String] val result: Map[String, NonEmptyList[Int]] = lines.nonEmptyTraverse(countWords)
  15. 15. Commutativity trait UnorderedFoldable[T[_]] { def unorderedFold[M: CommutativeMonoid](ta: T[M]): M } trait UnorderedTraverse[T[_]] { def unorderedSequence[F[_]: CommutativeApplicative, A] (ta: T[F[A]]): F[T[A]] }
  16. 16. Commutativity CommutativeMonoid: a |+| b <-> b |+| a E.g: Int, Set[String]
  17. 17. Commutativity val users: HashSet[User] val result = users.unorderedFoldMap(_.billableHours)
  18. 18. Commutativity CommutativeApplicative: map2(fa, fb)((a, b) => f(a, b)) <-> map2(fb, fa)((b, a) => f(a, b)) fa *> fb <-> fb <* fa E.g. Option, ValidatedNes
  19. 19. Commutativity type ValidatedNes[E, A] = Validated[NonEmptySet[E], A] val result: ValidatedNes[Error, RDD[User]] = users.unorderedTraverse(validate)
  20. 20. FlatTraverse trait Traverse[T[_]] { def flatTraverse[G[_]: Applicative, A, B](ta: T[A]) (f: A => G[T[B]])(implicit T: FlatMap[T]): G[T[B]] } ta.traverse(f).map(_.flatten) <-> ta.flatTraverse(f)
  21. 21. FlatTraverse val maybeUser: Option[User] def fetchAddress(user: User): IO[Option[Address]] val result: IO[Option[Address]] = maybeUser.flatTraverse(fetchAddress)
  22. 22. Parallelism trait Parallel[M[_]: Monad, F[_]: Applicative] { def parallel: FunctionK[M, F] def sequential: FunctionK[F, M] } def parSequence[T[_], M[_], F[_], A](ta: T[M[A]]) (implicit P: Parallel[M, F], T: Traverse[T]): M[T[A]]
  23. 23. Revisiting the teaser def request(userId: Int): EitherT[IO, Nel[Error], User] val result: IO[EitherNel[Error, List[User]]] = userIdList.parTraverse(request).value EitherT[IO, E, A] ⇔ Nested[ParIO, Validated[E, ?], A] IO[Either[E, A]] ⇔ ParIO[Validated[E, A]]
  24. 24. Other traversals traverseWithIndex unorderedFlatTraverse traverseWithIndexM traverse_ parFlatTraverse nonEmptyFlatTraverse parNonEmptyFlatTraverse parNonEmptyUnorderedTraverse
  25. 25. Other traversals traverseWithIndex unorderedFlatTraverse traverseWithIndexM traverse_ parFlatTraverse nonEmptyFlatTraverse parNonEmptyFlatTraverse parNonEmptyUnorderedTraverse + Sequence versions + Almost every other conceivable combination
  26. 26. Useful stuff! def parNonEmptyUnorderedSequence [T[_]: NonEmptyUnorderedTraverse, M[_], F[_], A](ta: T[M[A]]) (implicit P: NonEmptyCommutativeParallel[M, F]): M[T[A]] What could possibly be an instance for this??? NonEmptyUnorderedTraverse: NonEmptyRDD, NonEmptyHashMap NonEmptyCommutativeParallel: NonEmptyList ⇔ NonEmptyZipList
  27. 27. Useful stuff! def groupByNem[A, K](nel: NonEmptyList[A]) (f: A => K): NonEmptyHashMap[K, NonEmptyList[A]] users.groupByNem(_.address) .parNonEmptyUnorderedSequence
  28. 28. Summary Traverse is a great abstraction, because it allows us to traverse data structures using Applicative Functors, which represent independent computations. These traversals capture the essence of imperative loops over these data structures. We can add or remove constraints to the action(F[_]) or the structure types(T[_]) to get slightly different traversals.
  29. 29. Thank you all for listening! Twitter: @LukaJacobowitz GitHub: LukaJCB