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.

Refactoring Functional Type Classes

5,516 views

Published on

For decades, the Functor, Monoid, and Foldable type class hierarchies have dominated functional programming. Implemented in libraries like Scalaz and Cats, these type classes have an ancient origin in Haskell, and they have repeatedly proven useful for advanced functional programmers, who use them to maximize code reuse and increase code correctness.

Yet, as these type classes have been copied into Scala and aged, there is a growing awareness of their drawbacks, ranging from being difficult to teach to weird operators that don’t make sense in Scala (ap from Applicative), to overlapping and lawless type classes (Semigroupal), to a complete inability to abstract over data types that possess related structure (such as isomorphic applicatives).

In this presentation, John A. De Goes introduces a new Scala library with a completely different factoring of functional type classes—one which throws literally everything away and starts from a clean slate. In this new factoring, type classes leverage Scala’s strengths, including variance and modularity. Pieces fit together cleanly and uniformly, and in a way that satisfies existing use cases, but enables new ones never before possible. Finally, type classes are named, organized, and described in a way that makes teaching them easier, without compromising on algebraic principles.

If you’ve ever thought functional type classes were too impractical or too confusing or too restrictive, now’s your chance to get a fresh perspective on a library that just might make understanding functional programming easier than ever before!

Published in: Technology

Refactoring Functional Type Classes

  1. 1. SF Scala Meetup - July 30, 2020 John A. De Goes — @jdegoes Adam Fraser — @adamfraser Refactoring Functional Type Classes
  2. 2. WHY YOU’RE HERE “Let me tell you why you’re here. You’re here because you know something. What you know you can’t explain, but you feel it. You’ve felt it your entire life, that there’s something wrong with the world. You don’t know what it is, but it’s there, like a splinter in your mind, driving you mad. It is this feeling that has brought you to me. Do you know what I’m talking about?” — Morpheus, The Matrix
  3. 3. TABLE OF CONTENTS 01 THE LEGEND OF FUNCTOR The supreme reign of the Haskell functor hierarchy 02 TROUBLE IN FUNCTOR TOWN Drawbacks of the classic functor hierarchy 03 EASY ALGEBRA Unlike category theory, you already know algebra 04 TOUR OF ZIO PRELUDE An algebraic, modular, & Scala-first basis for type classes
  4. 4. THE LEGEND OF FUNCTOR
  5. 5. Functor THE LEGEND OF FUNCTOR trait Functor[F[_]] { def map[A, B](fa: F[A])(f: A => B): F[B] }
  6. 6. Functor -> Apply THE LEGEND OF FUNCTOR trait Apply[F[_]] extends Functor[F] { def map[A, B](fa: F[A])(f: A => B): F[B] def ap[A, B](ff: F[A => B])(f: F[A]): F[B] }
  7. 7. Functor -> Apply -> Applicative THE LEGEND OF FUNCTOR trait Applicative[F[_]] extends Apply[F] { def map[A, B](fa: F[A])(f: A => B): F[B] def ap[A, B](ff: F[A => B])(f: F[A]): F[B] def pure[A](a: A): F[A] }
  8. 8. Functor -> Apply -> Applicative -> Monad THE LEGEND OF FUNCTOR trait Monad[F[_]] extends Applicative[F] { def map[A, B](fa: F[A])(f: A => B): F[B] def ap[A, B](ff: F[A => B])(f: F[A]): F[B] def pure[A](a: A): F[A] def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B] } *Bind is skipped.
  9. 9. THE LEGEND OF FUNCTOR The Ubiquity of Functor in Functional Programming ● Haskell ● Scala ○ Scalaz ○ Cats ● Kotlin ● Java ● F# ● Idris ● And many others! 9
  10. 10. Books Have Been Written About It THE LEGEND OF FUNCTOR
  11. 11. TROUBLE IN FUNCTOR TOWN
  12. 12. The Curse of Haskellisms TROUBLE IN FUNCTOR TOWN def ap[A, B]( ff: F[A => B], fa: F[A]): F[B] f :: Int -> Int -> Int -> Int f <$> arg1 <*> arg2 <*> arg3
  13. 13. Set Is Not A “Functor”? TROUBLE IN FUNCTOR TOWN set.map(f).map(g) set.map(f andThen g) f g f.andThen(g) A B C A C
  14. 14. The Functor Hierarchy Is A Lie! TROUBLE IN FUNCTOR TOWN Functor in FP Functor in Math
  15. 15. ZIO Config TROUBLE IN FUNCTOR TOWN (string(“server”) |@| int(“port”))( Config.apply(_), Config.unapply(_))
  16. 16. ZIO Codec TROUBLE IN FUNCTOR TOWN lazy val js: Codec[Val] = ((spacing, ()) ~> (obj | arr | str | `true` | `false` | `null` | num) <~ ((), spacing))
  17. 17. Introspectable Monads TROUBLE IN FUNCTOR TOWN for { bool <- boolParser value <- if (bool) parserA else parserB } yield value
  18. 18. Constrained DSLs TROUBLE IN FUNCTOR TOWN def map[A, B](fa: F[A])(f: A => B): F[B] val fa: F[A] = … val f : A => B = … fa.map(a => f(a)) Type B is unconstrainable!
  19. 19. Invariance Pain TROUBLE IN FUNCTOR TOWN val functorDog: F[Dog] = … val functorAnimal: F[Animal] = functorDog ERROR!!!
  20. 20. Invariance Pain TROUBLE IN FUNCTOR TOWN val functorDog: F[Dog] = … val functorAnimal: F[Animal] = functorDog.widen[Animal]
  21. 21. Stack-Exploding Strictness TROUBLE IN FUNCTOR TOWN def forever[A](fa: F[A]): F[A] = fa *> forever(fa)
  22. 22. Lawless Type Classes & Operations TROUBLE IN FUNCTOR TOWN Defer Foldable Monad#tailRecM Parallel NonEmptyParallel UnorderedFoldable
  23. 23. Type Class Proliferation TROUBLE IN FUNCTOR TOWN Align Alternative Always Applicative ApplicativeError Apply Bifoldable Bifunctor Bimonad Bitraverse CoflatMap CommutativeApplicative CommutativeApply CommutativeFlatMap CommutativeMonad Comonad Contravariant ContravariantMonoidal ContravariantSemigroupal Defer Distributive Eval EvalGroup EvalMonoid EvalSemigroup FlatMap Foldable Functor FunctorFilter Inject InjectK Invariant InvariantMonoidal InvariantSemigroupal Later Monad MonadError MonoidK NonEmptyParallelNonEmptyR educible NonEmptyTraverse NotNull Now Parallel Reducible Representable SemigroupK Semigroupal Show StackSafeMonad Traverse TraverseFilter UnorderedFoldable UnorderedTraverse
  24. 24. Type Class Proliferation TROUBLE IN FUNCTOR TOWN Align Alternative Always Applicative ApplicativeError Apply Bifoldable Bifunctor Bimonad Bitraverse CoflatMap CommutativeApplicative CommutativeApply CommutativeFlatMap CommutativeMonad Comonad Contravariant ContravariantMonoidal ContravariantSemigroupal Defer Distributive Eval EvalGroup EvalMonoid EvalSemigroup FlatMap Foldable Functor FunctorFilter Inject InjectK Invariant InvariantMonoidal InvariantSemigroupal Later Monad MonadError MonoidK NonEmptyParallelNonEmptyR educible NonEmptyTraverse NotNull Now Parallel Reducible Representable SemigroupK Semigroupal Show StackSafeMonad Traverse TraverseFilter UnorderedFoldable UnorderedTraverse *meme from dev.to
  25. 25. Maddening Monad Transformers TROUBLE IN FUNCTOR TOWN type MyApp[E, W, S, R, A] = OptionT[ EitherT[ WriterT[ StateT[ Kleisli[Task, R, *], S, *], W, *], E, *], *]
  26. 26. TROUBLE IN FUNCTOR TOWN Is There Another Way?
  27. 27. EASY ALGEBRA
  28. 28. EASY ALGEBRA // Set of elements type Int // Operations on those elements def plus(x: Int, y: Int): Int // Laws about those operations x + (y + z) == (x + y) + z x + y == y + x You Already Know This
  29. 29. EASY ALGEBRA Associativity // Set of elements type A // Operations on those elements def combine(l: A, r: A): A // Laws about those operations a1 <> (a2 <> a3) == (a1 <> a2) <> a3
  30. 30. EASY ALGEBRA Identity // Set of elements type A // Operations on those elements def combine(l: A, r: A): A val identity: A // Laws about those operations a <> identity == a identity <> a == a
  31. 31. EASY ALGEBRA Commutativity // Set of elements type A // Operations on those elements def combine(l: A, r: A): A // Laws about those operations a1 <> a2 == a2 <> a1
  32. 32. EASY ALGEBRA
  33. 33. EASY ALGEBRA Standard Types // Associative, commutative, identity def combine(l: Int, r: Int): Int = l + r val identity: Int = 0 // Associative, identity def combine(l: String, r: String): String = l + r val identity: String = “”
  34. 34. EASY ALGEBRA Business Domain Specific Types final case class Csv( rows: Vector[Vector[String]], headers: Map[String, Int] ) // Associative, identity def combine(l: Csv, r: Csv): Csv = ???
  35. 35. TOUR OF ZIO PRELUDE > PRELUDE
  36. 36. ZIO Prelude is a small library that brings a common, useful algebraic abstractions & data types to Scala developers. ZIO Prelude is an alternative to libraries like Scalaz and Cats based on radical ideas that embrace modularity & subtyping in Scala and offer new levels of power and ergonomics. TOUR OF ZIO PRELUDE
  37. 37. TOUR OF ZIO PRELUDE Radical Orthogonal Principled Scala-First Minimal Pragmatic Accessible Opinionated Guiding Design Principles
  38. 38. TOUR OF ZIO PRELUDE Data Structures & Patterns for Traversing List[A], Option[A], ... Patterns of Composition for Types (A, A) => A Patterns of Composition for Type Constructors (F[A], F[A]) => F[A] Three Areas of Focus
  39. 39. TOUR OF ZIO PRELUDE The Anti-Modularity of the Functor Hierarchy Functor Applicative, Monad, etc. Trouble starts here!
  40. 40. TOUR OF ZIO PRELUDE The Anti-Modularity of the Functor Hierarchy Functions Composition The Classic Functor Hierarchy
  41. 41. TOUR OF ZIO PRELUDE The Anti-Modularity of the Functor Hierarchy trait Monad[F[_]] extends Applicative[F] { def map[A, B](fa: F[A])(f: A => B): F[B] def ap[A, B](ff: F[A => B])(f: F[A]): F[B] def pure[A](a: A): F[A] def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B] }
  42. 42. TOUR OF ZIO PRELUDE Detangling Functions from Composition trait Monad[F[_]] { def map[A, B](fa: F[A])(f: A => B): F[B] def zip[A, B](fa: F[A], fb: F[B]): F[(A, B)] def any: F[Any] def flatten[A](fa: F[F[A]]): F[A] }
  43. 43. TOUR OF ZIO PRELUDE The Modularity of the ZIO Prelude Hierarchy Functions Composition ZIO Prelude Hierarchy
  44. 44. TOUR OF ZIO PRELUDE The Modularity of the ZIO Prelude Hierarchy type Semigroup[A] = Associative[A] type CommutativeSemigroup[A] = Associative[A] with Commutative[A] type Monoid[A] = Identity[A] type CommutativeMonoid[A] = Commutative[A] with Identity[A] type Group[A] = Identity[A] with Inverse[A] type AbelianGroup[A] = Commutative[A] with Identity[A] with Inverse[A]
  45. 45. TOUR OF ZIO PRELUDE The Modularity of the ZIO Prelude Hierarchy type Functor[F[+_]] = Covariant[F] type Contravariant[F[-_]] = zio.prelude.Contravariant[F] type Invariant[F[_]] = zio.prelude.Invariant[F] type Alternative[F[+_]] = Covariant[F] with IdentityBoth[F] with IdentityEither[F] type InvariantAlt[F[_]] = Invariant[F] with IdentityBoth[F] with IdentityEither[F]
  46. 46. TOUR OF ZIO PRELUDE The Modularity of the ZIO Prelude Hierarchy type InvariantSemigroupal[F[_]] = Invariant[F] with AssociativeBoth[F] type Semigroupal[F[+_]] = Covariant[F] with AssociativeBoth[F] type ContravariantSemigroupal[F[-_]] = Contravariant[F] with AssociativeBoth[F] type SemigroupK[F[_]] = AssociativeEither[F] type MonoidK[F[_]] = IdentityEither[F] type ContravariantMonoidal[F[-_]] = Contravariant[F] with IdentityBoth[F] type InvariantMonoidal[F[_]] = Invariant[F] with IdentityBoth[F]
  47. 47. TOUR OF ZIO PRELUDE The Modularity of the ZIO Prelude Hierarchy type FlatMap[F[+_]] = Covariant[F] with AssociativeFlatten[F] type Monad[F[+_]] = Covariant[F] with IdentityFlatten[F] type Divide[F[-_]] = Contravariant[F] with AssociativeBoth[F] type Divisible[F[-_]] = Contravariant[F] with IdentityBoth[F] type Decidable[F[-_]] = Contravariant[F] with IdentityBoth[F] with IdentityEither[F] type Apply[F[+_]] = Covariant[F] with AssociativeBoth[F] type Applicative[F[+_]] = Covariant[F] with IdentityBoth[F] type InvariantApplicative[F[_]] = Invariant[F] with IdentityBoth[F]
  48. 48. TOUR OF ZIO PRELUDE trait Associative[A] { def combine(l: A, r: A): A } // a1 <> (a2 <> a3) == (a1 <> a2) <> a3
  49. 49. TOUR OF ZIO PRELUDE trait Identity[A] extends Associative[A] { def combine(l: A, r: A): A def identity: A } // a <> identity == a // identity <> a == a
  50. 50. TOUR OF ZIO PRELUDE trait Commutative[A] extends Associative[A] { def combine(l: A, r: A): A } // a1 <> a2 == a2 <> a1
  51. 51. TOUR OF ZIO PRELUDE trait Covariant[F[+_]] { def map[A, B](f: A => B): F[A] => F[B] } Variance Guarantees Automatic Widening
  52. 52. TOUR OF ZIO PRELUDE trait Contravariant[F[-_]] { def contramap[A, B](f: B => A): F[A] => F[B] } Variance Guarantees Automatic Narrowing
  53. 53. TOUR OF ZIO PRELUDE case class <=>[A, B]( to: A => B, from: B => A) trait Invariant[F[_]] { def invmap[A, B](f: A <=> B): F[A] <=> F[B] }
  54. 54. TOUR OF ZIO PRELUDE trait AssociativeBoth[F[_]] { def combine[A, B]( left : F[A], right: F[B]): F[(A, B)] } // zio1 zip zio2
  55. 55. TOUR OF ZIO PRELUDE trait AssociativeEither[F[_]] { def combine[A, B]( left : F[A], right: F[B]): F[Either[A, B]] } // zio1 orElseEither zio2
  56. 56. TOUR OF ZIO PRELUDE trait AssociativeFlatten[F[+_]] { def flatten[A](nested: F[F[A]]): F[A] } // zio.flatten
  57. 57. TOUR OF ZIO PRELUDE trait IdentityBoth[F[_]] extends AssociativeBoth[F] { def combine[A, B]( left : F[A], right: F[B]): F[(A, B)] def any: F[Any] } // zio1 zip zio2 // ZIO.unit
  58. 58. TOUR OF ZIO PRELUDE trait IdentityEither[F[_]] extends AssociativeBoth[F] { def combine[A, B]( left : F[A], right: F[B]): F[Either[A, B]] def none: F[Nothing] } // zio1 orElseEither zio2 // ZIO.halt(Cause.empty)
  59. 59. TOUR OF ZIO PRELUDE trait IdentityFlatten[F[+_]] extends AssociativeFlatten[F] { def flatten[A](nested: F[F[A]]): F[A] def any: F[Any] } // zio.flatten // ZIO.unit
  60. 60. TOUR OF ZIO PRELUDE trait CommutativeBoth[F[_]] extends AssociativeBoth[F] { def combine[A, B]( left : F[A], right: F[B]): F[(A, B)] } // zio1 zipPar zio2
  61. 61. TOUR OF ZIO PRELUDE trait CommutativeEither[F[_]] extends AssociativeEither[F] { def combine[A, B]( left : F[A], right: F[B]): F[Either[A, B]] } // zio1 raceEither zio2
  62. 62. TOUR OF ZIO PRELUDE trait Traversable[Data[+_]] { def foreach[Effect[_]: …, A, B](as: Data[A])( f: A => Effect[B]): Effect[Data[B]] } // requests.foreach { request => // handleRequest(request) // }
  63. 63. TOUR OF ZIO PRELUDE trait NonEmptyTraversable[Data[+_]] extends Traversable[Data] { def foreach1[Effect[_]: …, A, B](as: Data[A])( f: A => Effect[B]): Effect[Data[B]] }
  64. 64. TOUR OF ZIO PRELUDE Debug[-A] Equal[-A] Hash[-A] Ord[-A] Embracing Declaration-Site Variance implicit val ordAnimal: Ord[Animal] = … if (dog1 <= dog2) { // WORKS!!! }
  65. 65. TOUR OF ZIO PRELUDE NonEmptyList[A] Validation[E, A] ZSet[M, A] Embrace & Extend Scala Collections
  66. 66. TOUR OF ZIO PRELUDE trait ZPure[-StateIn, +StateOut, -Env, +Err, +Success] type State[S, +A] = ZPure[S, S, Any, Nothing, A] type EState[S, +E, +A] = ZPure[S, S, Any, E , A] No More Monad Transformers For when you think ZIO is great but just doesn’t have enough type parameters
  67. 67. TOUR OF ZIO PRELUDE type MyStack[S1, S2, R, E, A] = Kleisli[ ({ type lambda[a] = EitherT[ ({ type lambda[a] = IndexedStateT[Eval, S1, S2, a] })#lambda, E, a] })#lambda, R, A] The Alternative
  68. 68. TOUR OF ZIO PRELUDE // Monad Transformers def get[S, R, E]: MyStack[S, S, R, E, S] = { type SState[A] = State[S, A] type EitherESState[A] = EitherT[SState, E, A] val eitherT = EitherT.liftF[SState, E, S](State.get) Kleisli.liftF[EitherESState, R, S](eitherT) } // ZPure def get[S]: State[S, S] = State.get[S] Ergonomics
  69. 69. TOUR OF ZIO PRELUDE Performance
  70. 70. TOUR OF ZIO PRELUDE Newtypes object Meter extends Subtype[Int] type Meter = Meter.Type object Sum extends SubtypeF type Sum[A] = Sum.Type[A] object Natural extends NewtypeSmart[Int](isGreaterThanEqualTo(0)) type Natural = Natural.Type
  71. 71. TOUR OF ZIO PRELUDE Ergonomics List(1, 2, 3, 4, 5).foldMap(Sum(_)) // 15 List(1, 2, 3, 4, 5).foldMap(Prod(_)) // 120
  72. 72. ● Documentation ● More Instances ● More Polishing ● Effect Type Classes ● Performance Optimization ● Automatic Derivation for ADTs ● Get Feedback from Real Users NEXT STEPS
  73. 73. SPECIAL THANKS ● Dejan Mijic ● Sken ● Manfred Weber ● Jorge Aliss ● Phil Derome ● Kamal King ● Maxim Schuwalaw And Salar Rahmanian!
  74. 74. THANK YOU! Does anyone have any questions? github.com/zio/zio-prelude Get mentored: patreon.com/jdegoes Follow us: @jdegoes, @adamfraser

×