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!
CNIC Information System with Pakdata Cf In Pakistan
Refactoring Functional Type Classes
1. SF Scala Meetup - July 30, 2020
John A. De Goes — @jdegoes
Adam Fraser — @adamfraser
Refactoring Functional
Type Classes
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. 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
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. THE LEGEND OF
FUNCTOR
The Ubiquity of Functor in
Functional Programming
● Haskell
● Scala
○ Scalaz
○ Cats
● Kotlin
● Java
● F#
● Idris
● And many others!
9
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. 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. The Functor Hierarchy Is A Lie!
TROUBLE IN
FUNCTOR TOWN
Functor in FP
Functor in Math
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!
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. 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. 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. 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
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. 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 =
???
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. TOUR OF ZIO
PRELUDE
Radical Orthogonal Principled
Scala-First Minimal
Pragmatic
Accessible Opinionated
Guiding Design Principles
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. TOUR OF ZIO
PRELUDE
The Anti-Modularity of the Functor Hierarchy
Functor
Applicative, Monad, etc.
Trouble starts here!
40. TOUR OF ZIO
PRELUDE
The Anti-Modularity of the Functor Hierarchy
Functions Composition
The Classic Functor
Hierarchy
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. 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. TOUR OF ZIO
PRELUDE
The Modularity of the ZIO Prelude Hierarchy
Functions Composition
ZIO Prelude
Hierarchy
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. 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. 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. 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. TOUR OF ZIO
PRELUDE
trait Associative[A] {
def combine(l: A, r: A): A
}
// a1 <> (a2 <> a3) == (a1 <> a2) <> a3
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. TOUR OF ZIO
PRELUDE
trait Commutative[A] extends Associative[A] {
def combine(l: A, r: A): A
}
// a1 <> a2 == a2 <> a1
51. TOUR OF ZIO
PRELUDE
trait Covariant[F[+_]] {
def map[A, B](f: A => B):
F[A] => F[B]
}
Variance Guarantees Automatic Widening
52. TOUR OF ZIO
PRELUDE
trait Contravariant[F[-_]] {
def contramap[A, B](f: B => A):
F[A] => F[B]
}
Variance Guarantees Automatic Narrowing
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. TOUR OF ZIO
PRELUDE
trait AssociativeBoth[F[_]] {
def combine[A, B](
left : F[A],
right: F[B]): F[(A, B)]
}
// zio1 zip zio2
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. TOUR OF ZIO
PRELUDE
trait AssociativeFlatten[F[+_]] {
def flatten[A](nested: F[F[A]]): F[A]
}
// zio.flatten
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. 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)
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. 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. 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
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
72. ● Documentation
● More Instances
● More Polishing
● Effect Type Classes
● Performance Optimization
● Automatic Derivation for ADTs
● Get Feedback from Real Users
NEXT STEPS
73. SPECIAL THANKS
● Dejan Mijic
● Sken
● Manfred Weber
● Jorge Aliss
● Phil Derome
● Kamal King
● Maxim Schuwalaw
And Salar Rahmanian!
74. THANK YOU!
Does anyone have any questions?
github.com/zio/zio-prelude
Get mentored: patreon.com/jdegoes
Follow us: @jdegoes, @adamfraser