Findings, Interesting Topics About FP & Scala with 🐈🐱

Content

  • Terminology
  • Type Classes
  • Monads
  • Monad Transformers
  • Functors
  • Applicatives
  • Monoids
  • Kleislis
  • ADTs

Terminology

Type Classes

trait Showable[A] {
  def show(a: A): String
}
  • Provides methods for a Type A

Advantages:

  • ad hoc polymorphism
  • Compile-time safety:
java.util.UUID.randomUUID().show
> could not find implicit value for parameter showable: Showable[java.util.UUID]

Monads

  • anything with a constructor and a flatMap method
  • mechanism for sequencing computations

Benefits of Monadic Design

  • Avoid boilerplate code

    • Option
  • Seperation of concern

    • Option handles 'coping mechanism'
  • Every monad is also a functor

    • rely on both flatMap and map to sequence computations
  • for comprehensions

trait Monad[F[_]] {
  def pure[A](a: A): F[A] // wrap it

  def flatMap[A, B](value: F[A])(func: A => F[B]): F[B]
  // apply a transformation function to a monadic value

  def map[A, B](value: F[A])(func: A => B): F[B] =
    flatMap(value)(a => pure(func(a)))
    // apply a non-monadic function to a monadic value
}

50% center

Cool Monads from scalaz

Monad Transformers

  • Avoid nested for comprehensions
  • Compose Option Future -> OptionT[Future, A]
  • Confusion on how liftM works
    • lift a value of Future[Option[A]] into an OptionT[Future, A]
      Before:
  { maybeUser: Option[Duser3] =>
      val optionTshi = (for {
        user  <- maybeUser.liftM[OptionT]
        referralCode <-  OptionT.some(referralService.getOrCreateUserReferralCode(user))
      } yield referralCode)
​    }

After:

{
    maybeUser: Option[Duser3] =>
      (for {
        user  <- OptionT(Future.value(maybeUser))
        referralCode <-  referralService.getOrCreateUserReferralCode(user).liftM[OptionT]
      } yield referralCode).run.void
  }

Functor

  • anything with a .map method
  • single argument functions are also functors
  • Can be composed (first do this, then that)
val listOption = List(Some(1), None, Some(2))
// listOption: List[Option[Int]] = List(Some(1), None, Some(2))

// Through Functor#compose
Functor[List].compose[Option].map(listOption)(_ + 1)
// res1: List[Option[Int]] = List(Some(2), None, Some(3))
  • Laws:
    • Composition: fa.map(f).map(g) is fa.map(f.andThen(g))
    • Identity: Mapping with the identity function is a no-op
      fa.map(x => x) = fa
  • Allow lifting pure to effectful function
     def lift[A, B](f: A => B): F[A] => F[B] = fa => map(fa)(f)
    
// Example implementation for Option
implicit val functorForOption: Functor[Option] = new Functor[Option] {
  def map[A, B](fa: Option[A])(f: A => B): Option[B] = fa match {
    case None    => None
    case Some(a) => Some(f(a))
  }
}

Applicatives

  • Applicative extends Functor with an ap and pure method.
  • Wrap functions in Contexts!
  • mapN to apply for different arities
  • avoid making unnecessary claims about dependencies between computations
    • using |@|
  • ap unpacks both Monads, and then applies the function to the value:
case class Foo(s: Symbol, n: Int)

val maybeFoo = for {
  s <- maybeComputeS(whatever)
  n <- maybeComputeN(whatever)
} yield Foo(s, n)

⬇️

val maybeFoo = maybeComputeS(whatever).flatMap(
  s => maybeComputeN(whatever).map(n => Foo(s, n))
)

maybeComputeN never depends on s, compiler still thinks so

val maybeFoo = (maybeComputeS(whatever) |@| maybeComputeN(whatever))(Foo(_, _))
val url = new URL(urlStr)
(S3Bucket(url) |@| S3Key(url)) { (bucket, key) =>
  if (bucket == store.bucket) {
    key.some
  } else {
    warn(s"can not delete a resource $url from unknown bucket: $bucket")
    none
  }
}.flatten.toList

Monoids

trait Monoid[A] {
  def combine(x: A, y: A): A
  def empty: A
}

Examples of Monoids:

  • Ints, with the zero being 0 and the operator being +.

  • Ints, with the zero being 1 and the operator being *.

  • Lists, with the zero being Nil and the operator being ++.

  • Strings, with the zero being "" and the operator being +.

  • Q: give me some A for which I have a monoid

Usage

trait TotalInstances {

  implicit def totalMonoid(implicit targetCcy: Currency, mc: MoneyContext): Monoid[Total] =
    Monoid.instance[Total]({ (a, b) =>
      a + b
    }, zero)

  def zero(implicit targetCcy: Currency, mc: MoneyContext): Total = {
    val zeroCurrency = targetCcy.apply(0).toResponse
    Total(
      total = zeroCurrency,
      product = zeroCurrency,
      realProduct = zeroCurrency,
      duties = None,
      insurance = None,
      returns = None,
      shipping = zeroCurrency,
      taxes = None,
      discount = None
    )
  }
}

Kleisli

val f: Int => Task[String] = ???
val fKleisli: Kleisli[Task, Int, String] = ???

Like a monad Transformer for function composition.
--> The advantage is you can do it within the context of a Future / Task, etc.

andThenK does automatic lifting.

Call run to extract again.

import cats.data._
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import cats.instances.future._

object KleisliTest extends App {
  val getNumberFromDb: Unit => Future[Int]  = _ => Future.successful(2)
  val processNumber: Int => Future[Int]     = num => Future.successful(num * 2)
  val persistToDb: Int => Future[Boolean]   = _ => Future.successful(true)

  val kleisliCombo: Kleisli[Future, Unit, Boolean] = 
    Kleisli(getNumberFromDb)
    andThen processNumber
    andThen persistToDb

  val unpacked: Unit => Future[Boolean] = kleisliCombo.run

  unpacked().map(println)
}

Algebraic Data Types

  • A sum type consisting of various subtypes of other sum and product types.
  • Analyze values of algebraic data with pattern matching.

Product Type

case class Student(enrolled: Boolean, age: Byte)
// Students consists of a Boolean AND Byte
// 2 * 256 = 258

Sum Type

sealed trait Color 
case object Red extends Color
case object Blue extends Color
case object Green extends Color
// Color can be Red OR Blue OR Green
// 1 + 1 + 1 = 3 distinct possible values

Example

  • Either
    • move forward X meters
    • rotate Y degrees

Naive implemenation:

final case class Command(label: String, meters: Option[Int], degrees: Option[Int])

What are the issues?

Illegal states

Command("foo", None, None)
Command("bar", Some(1), Some(2))

Reworked

sealed abstract class Command extends Product with Serializable

object Command {
  final case class Move(meters: Int) extends Command
  final case class Rotate(degrees: Int) extends Command
}

Bonus: Pattern Matching

def print(cmd: Command) = cmd match {
  case Command.Move(dist)    => println(s"Moving by ${dist}m")
  case Command.Rotate(angle) => println(s"Rotating by ${angle}°")
}

Thanks!