Skip to content

Instantly share code, notes, and snippets.

@arosien
Last active May 20, 2020 18:20
Show Gist options
  • Save arosien/76af9e989d5c9dd53a24112ffb16c38e to your computer and use it in GitHub Desktop.
Save arosien/76af9e989d5c9dd53a24112ffb16c38e to your computer and use it in GitHub Desktop.
"Functional Programming Demystification" @ Scala at the Sea, 19 May 2020

Functional Programming Demystification

Functional programming is full of Fancy Words™. Let's collect a list of them to demystify together. Adam will facilitate with live-coding, and will also try to make good jokes.

Here's some terms to bootstrap us: effect, free monad, functor, property-based testing, refinement type, stream, etc.

We will have plenty of time for Q&A during this session.

"Essential Effects" Course Now Available!

https://www.inner-product.com/services/training/essential-effects/

How to safely create, compose, and execute effectful Scala programs using the Typelevel cats-effect library.

TODO

The audience suggested starting with functor, monad, and effect, so here we go!

Functor

  • captures the notion of map: List, Option, Future all have map
  • see worksheet for code

Aside: Typeclasses and Their Laws

  • typeclass: interface + implementations for various types

"How do we know an implementation of a typeclass is correct?"

For Functor.map, how do we know it works? Laws!

Laws for Functor:

  • identity law: map(identity) = identity
    • "doing nothing should really do nothing"
  • composition: fa.map(f).map(g) == fa.map(f andThen g)
    • "mapping twice should be the same as mapping once with composed transformations"

Monad

  • captures the notion of flatMap (and pure)
  • see worksheet for code

Effect

What is an effect? Is it different than a side-effect, which we know is "bad" or "dangerous"?

Examples:

  • "passing state after the computation" == state effect, e.g., incrementing a counter when executing something; see worksheet
  • "NullPointerException effect" == Option
  • "Error" effect == Either[L, R]
  • Dependency Injection == Reader
  • println(), a.k.a., logging == Writer
  • asynchronous computation == scala.concurrent.Future? Future is not referentially transparent! This can bite you or your friend.
  • cats.effect.IO: do anything, even side-effects!

BIG IDEAS in effects:

  • replace side-effects with more structure, more laws, immutability, etc., to make them safe
  • the effect is apparent in the type signature; this lets us "see" that something is going on, as opposed to side-effects
  • we separate the description of what we want to do from execution of that description

Resources

1 + 13
//
// Functor
//
List(1, 2, 3).map(_ + 1)
Option(1).map(_.toString)
// typeclass
trait Functor[F[_]] {
def map[A, B](fa: F[A])(f: A => B): F[B]
def void[A](fa: F[A]): F[Unit] =
map(fa)(a => ())
}
// laws
class FunctorLaws[F[_]](f: Functor[F]) {
def identityLaw[A](fa: F[A]) =
f.map(fa)(identity) == fa
def composition[A, B, C](fa: F[A], a2b: A => B, b2c: B => C) =
f.map(f.map(fa)(a2b))(b2c) == f.map(fa)(a2b andThen b2c)
}
// typeclass instances
implicit val listFunctor: Functor[List] =
new Functor[List] {
def map[A, B](fa: List[A])(f: A => B): List[B] = fa.map(f)
}
implicit val optionFunctor: Functor[Option] =
new Functor[Option] {
def map[A, B](fa: Option[A])(f: A => B): Option[B] = fa.map(f)
}
// val intFunctor: Functor[Int] = ???
// the idea: _abstract_ over F containers that you can map over
def addOne[F[_]](is: F[Int])(implicit functor: Functor[F]): F[Int] =
functor.map(is)(_ + 1)
addOne(List(1, 2, 3))
addOne(Option(1))
//
// Monad
//
trait Monad[F[_]] {
def pure[A](a: A): F[A]
def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B]
}
//
// Effects
//
// example: replace mutable counter with immutable "state" effect
var numAddOne = 0
// pass initial counter state in, next counter state out, along with the actual computation
def addOne2[F[_]](is: F[Int])(count: Long)(implicit functor: Functor[F]): F[(Long, Int)] =
functor.map(is)(i => (count + 1, i + 1))
def addOne2[F[_], S](is: F[Int])(state: S, f: S => S)(implicit functor: Functor[F]): F[(S, Int)] =
functor.map(is)(i => (f(state), i + 1))
// eventually you can refactor this ^^^ to be some datatype like State or StateT that couples the state change with the actual computation
case class StateT[F[_], S, A](f: S => F[(S, A)])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment