Skip to content

Instantly share code, notes, and snippets.

@sakshamsharma
Created June 4, 2018 14:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save sakshamsharma/11cb3ad513125a8979c527adcd0b5a4f to your computer and use it in GitHub Desktop.
Save sakshamsharma/11cb3ad513125a8979c527adcd0b5a4f to your computer and use it in GitHub Desktop.
FP-for-mortals notes while reading

Chapter 1: Introduction

Defining methods on a class

object Execution {
  implicit class Ops[A, C[_]](c: C[A]) {
    def flatMap[B](f: A => C[B])(implicit e: Execution[C]): C[B] =
          e.doAndThen(c)(f)
    def map[B](f: A => B)(implicit e: Execution[C]): C[B] =
          e.doAndThen(c)(f andThen e.create)
  }
}

def echo[C[_]](implicit t: Terminal[C], e: Execution[C]): C[String] =
  t.read.flatMap { in: String =>
    t.write(in).map { _: Unit =>
      in
    }
  }

This way, we define method flatMap on objects of type C[A] if Execution is implicit.

Chapter 2: For Comprehensions

De-mystifying a given syntactic sugar

scala> import scala.reflect.runtime.universe._
scala> val a, b, c = Option(1)
scala> show { reify {
         for { i <- a ; j <- b ; k <- c } yield (i + j + k)
       } }

res:
$read.a.flatMap(
  ((i) => $read.b.flatMap(
    ((j) => $read.c.map(
      ((k) => i.$plus(j).$plus(k)))))))

Even typ-matching in for-syntax can trigger filter

reify> for { i: Int <- a } yield i

a.withFilter {
  case i: Int => true
  case _      => false
}.map { case i: Int => i }

Using Monad transformers in generic cases

scala> def getC: Future[Int] = ...
scala> def getD: Option[Int] = ...
scala> val result = for {
         a <- OptionT(getA)
         b <- OptionT(getB)
         c <- getC.liftM[OptionT]
         d <- OptionT(getD.pure[Future])
       } yield (a * b) / (c * d)
result: OptionT[Future, Int] = OptionT(Future(<not completed>))

Fancy |> syntax

|> (thrush operator) applies the function on the right on the value on the left.

scala> val result = for {
         a <- getA       |> liftFutureOption
         b <- getB       |> liftFutureOption
         c <- getC       |> liftFuture
         d <- getD       |> liftOption
         e <- 10         |> lift
       } yield e * (a * b) / (c * d)
result: OptionT[Future, Int] = OptionT(Future(<not completed>))

Chapter 3: Application Design

Running monadic stuff in parallel

def initial: F[WorldView] =
  ^^^^(D.getBacklog, D.getAgents, M.getManaged, M.getAlive, M.getTime) {
    case (db, da, mm, ma, mt) => WorldView(db, da, mm, ma, Map.empty, mt)
  }
// Or:
// (D.getBacklog |@| D.getAgents |@| M.getManaged |@| M.getAlive |@| M.getTime)

This is supported by the Future monad but not the Id monad.

Chapter 4: Data and Functionality

Data

Exhaustivity

The compiler will not perform exhaustivity checking if the class is not sealed or if there are guards, like =case Foo(bar) if bar => “cool”= To remain safe, don’t use guards on sealed types.

More about GADTs

A cleaner syntax to define nested Either types is to create an alias type ending with a colon, allowing infix notation with association from the right:

type |:[L,R] = Either[L, R]
X.type |: Y.type |: Z
type Accepted = String |: Long |: Boolean

Complexity

In FP, functions are total and must return an instance for every input, no Exception. Minimising the complexity of inputs and outputs is the best way to achieve totality. As a rule of thumb, it is a sign of a badly designed function when the complexity of a function’s return value is larger than the product of its inputs: it is a source of entropy.

Algebra

(A => C) => ((B => C) => C)

We can convert and rearrange

(c ^ (c ^ b)) ^ (c ^ a)
= c ^ ((c ^ b) * (c ^ a))
= c ^ (c ^ (a + b))

then convert back to types and get

(Either[A, B] => C) => C

which is much simpler: we only need to ask the users of our framework to provide a Either[A, B] => C.

Functionality

Extension methodology

implicit class DoubleOps(x: Double) {
  def sin: Double = math.sin(x)
}
1.0.sin

The above has a runtime effect since it compiles to this and creates a DoubleOps for no long term use:

implicit def DoubleOps(x: Double): DoubleOps = new DoubleOps(x)
class DoubleOps(x: Double) {
  def sin: Double = java.lang.Math.sin(x)
}

Prefer this new version:

implicit final class DoubleOps(val x: Double) extends AnyVal {
  def sin: Double = java.lang.Math.sin(x)
}

Typeclasses

Fancy way using Simulacrum. This allows using the T: Numeric trait directly.

import simulacrum._

@typeclass trait Ordering[T] {
  def compare(x: T, y: T): Int
  @op("<") def lt(x: T, y: T): Boolean = compare(x, y) < 0
  @op(">") def gt(x: T, y: T): Boolean = compare(x, y) > 0
}

@typeclass trait Numeric[T] extends Ordering[T] {
  @op("+") def plus(x: T, y: T): T
  @op("*") def times(x: T, y: T): T
  @op("unary_-") def negate(x: T): T
  def zero: T
  def abs(x: T): T = if (lt(x, zero)) negate(x) else x
}

import Numeric.ops._
def signOfTheTimes[T: Numeric](t: T): T = -(t.abs) * t

But usually we do this:

trait Ordering[T] {
  def compare(x: T, y: T): Int
  def lt(x: T, y: T): Boolean = compare(x, y) < 0
  def gt(x: T, y: T): Boolean = compare(x, y) > 0
}

And use like this:

def signOfTheTimes[T](t: T)(implicit N: Numeric[T]): T = {
  import N._
  times(negate(abs(t)), t)
}

Instances are defined as:

implicit val NumericDouble: Numeric[Double] = new Numeric[Double] {
    def plus(x: Double, y: Double): Double = x + y
    ...
    def compare(x: Double, y: Double): Int = java.lang.Double.compare(x, y)
    // optimised
    override def lt(x: Double, y: Double): Boolean = x < y
    ...
  }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment