Scalaz 102 

Level Up Your Scalaz Fu

Colt Frederickson

@coltfred

Who am I?

  • Working on a small "Big Data" team at Oracle
  • Writing Scala since 2011
  • Functional Programming Convert
  • Haskell Dabbler

Ask Questions!

What is Scalaz?

What is Scalaz?

What is Scalaz?

trait MonadTrans[F[_[_], _]] {
  ...
}

What is Scalaz?

type ->[A, B] = (({type λ[α]=A})#λ) ~> (({type λ[α]=B})#λ)

No Really...

  • Data Types
    • IO
    • Task
    • \/
    • NonEmptyList
    • etc.
  • Better abstractions (type classes)
    • Functor
    • Applicative
    • Monad
    • Foldable
    • Traverse
    • etc.

Scalaz Package Structure

//Data types are under scalaz
import scalaz.NonEmptyList
import scalaz.Kleisli
import scalaz.Zipper
import scalaz.Monad
import scalaz.Functor
//etc

//Or get them all
import scalaz._

Data Types

Scalaz Package Structure

// Adds .right and .left to everything.
import scalaz.syntax.either._

// Adds =/= and === to things that have an Equal typeclass. 
import scalaz.syntax.equal._ 

// Adds .map, strengthL, strengthR to things with Functor typeclass.
import scalaz.syntax.functor._ 

//Or get them all!
import scalaz.syntax.all._

Syntax

Scalaz Package Structure

// Adds .intersperse, .filterM, etc.
import scalaz.syntax.std.list._  

// Adds .toRightDisjunction, etc.
import scalaz.syntax.std.option._ 

//Or get them all!
import scalaz.syntax.std.all._

Syntax

Scalaz Package Structure

//Bring in instances for std lib types
import scalaz.std.option._
import scalaz.std.list._
//etc

Typeclass Instances

https://www.gliffy.com/go/publish/4950560

Let's Dig In!

  • Types
    • NonEmptyList
    • Validation
  • Typeclasses
    • Semigroup​
    • Monoid
    • Foldable
    • Traverse

OneAnd

final case class OneAnd[F[_], A](head: A, tail: F[A])

//More concretely
type NonEmptyList[A] = OneAnd[List,A]

//Scalaz provides an actual type for NEL though!
final class NonEmptyList[+A] private[scalaz](val head: A, val tail: List[A])

Definition:

NonEmptyList

Use the types! Don't be a jerk!

NonEmptyList


def nel[A](h: A, t: List[A]): NonEmptyList[A] = new NonEmptyList(h, t)

def nels[A](h: A, t: A*): NonEmptyList[A] = nel(h, t.toList)

Constructors:

Operations:

//Roughly the same as List, except...
val head: A

//In case of emergency you can just call 
def list: List[A] = head :: tail

NonEmptyList

//Shamelessly stolen from Atto

//0 to N matches
def many[A](p: => Parser[A]): Parser[List[A]] 

//1 to N matches
def many1[A](p: => Parser[A]): Parser[NonEmptyList[A]] 

Parsers:

NonEmptyList


def validateUsers(l:List[User]): Validation[NonEmptyList[Error], List[User]] = ...

//OR

def validateUsers(l:List[User]): Validation[NonEmptyList[Error], NonEmptyList[User]] = ...

Validation:

NonEmptyList

BE PRECISE!!!

Validation

sealed abstract class Validation[+E, +A]

final case class Success[A](a: A) extends Validation[Nothing, A]
final case class Failure[E](e: E) extends Validation[E, Nothing]

Definition:

Validation

/** Wraps failures in a NonEmptyList */
def toValidationNel: ValidationNel[E, A]

/** Convert to a disjunction. */
def disjunction: (E \/ A) 

/** Throw away the error and return Nil or return the Success in a List */
def toList: List[A]

/** Catamorphism. Run the first given function if failure, otherwise, the second given function. */
def fold[X](fail: E => X, succ: A => X): X

//Many many more!

Operations:

Validation

Validation

//The type in [] is the *error* type on success.
(1.success[String] |@| 2.success[String]) {_ + _}
// Success(3)

(1.success[String] |@| "error".failure[Int]) {_ + _}
// Failure(error)

("error2".failure[Int] |@| "error".failure[Int]) {_ * _}
// Failure(error2error)

("error2".failureNel[Int] |@| "error".failureNel[Int]) {_ * _}
// Failure(NonEmptyList(error2,error))

Examples:

Typeclasses

 


trait Functor[F[_]] {
  def map[A](fa:F[A])(f: A => B): F[B]
}

implicit val listInstance = new Functor[List] {
  def map[A](fa:List[A])(f: A => B): List[B] = ...
}

implicit val optionInstance = new Functor[Option] {...}

Semigroup

 


trait Semigroup[F]  {
  // |+| is also used for this.
  def append(f1: F, f2: => F): F
}

Definition:

Laws:

// Associativity
(a |+| b) |+| c == a |+| (b |+| c)

// Or...
append(append(a, b), c) == append(a, append(b,c))

Semigroup

 


def updateAppendMap[A, B: Semigroup](m: Map[A,B])(k: A, v: B) = {
  val newValue = m.get(k).map(_ |+| v).getOrElse(v)
  m + (k -> newValue)
}

updateAppendMap(Map("A" -> List(1)))("A", List(2))
// Yields Map(A -> List(1, 2))

updateAppendMap(Map("A" -> List(1)))("B", List(2))
// Yields Map(A -> List(1), B -> List(2))

Example:

Monoid


trait Monoid[F] extend Semigroup[F] {
  def zero: F
}

Definition:

Laws:

// Associativity
(a |+| b) |+| c == a |+| (b |+| c)

append(append(a, b), c) == append(a, append(b,c))

// Left identity
a == append(zero, a)

// Right identity
a == append(a, zero)

Monoid

Gives us:

def multiply(value: F, n: Int): F =
  Stream.fill(n)(value).foldLeft(zero)((a,b) => append(a,b))

/** Whether `a` == `zero`. */
def isMZero(a: F)(implicit eq: Equal[F]): Boolean = eq.equal(a, zero)

final def ifEmpty[B](a: F)(t: => B)(f: => B)(implicit eq: Equal[F]): B =
  if (isMZero(a)) { t } else { f }

Monoid

Instances:

  • Int (Product)
  • Int (Sum)
  • List
  • String
  • Option (First)
  • Option (Last)
  • Tuples
  • Map

Monoid

Example :

import scalaz.Monoid
import scalaz.syntax.monoid._ // for |+|

def betterSum[A: Monoid](l: TraversableOnce[A]): A = l.foldLeft(Monoid[Option[Int]].zero)((acc, a) => acc |+| a)

betterSum(List("a","b","c"))
// Yields "abc"

betterSum(List(Some(1), Some(2), None))
// Yields Some(3)

Monoid

Example :

import scalaz.Monoid
import scalaz.syntax.monoid._ // for |+|
import scalaz.std.map._ // For Map instances

Map("A" -> 2) |+| Map("A" -> 3) 
// Yields Map("A" -> 5)

Map("A" -> 2) |+| Map("B" -> 3) 
// Yields Map("A" -> 2, "B" -> 3)



Monoid

Example :

import scalaz.Monoid
import scalaz.syntax.monoid._ // for |+|
import scalaz.std.map._ // For Map instances

Map("A" -> 2) |+| Map("A" -> 3) 
// Yields Map("A" -> 5)

Map("A" -> 2) |+| Map("B" -> 3) 
// Yields Map("A" -> 2, "B" -> 3)



Map is a Monoid if the values can be appended!

(if there is Semigroup for them)

Monoid

Foldable


trait Foldable[F[_]]  {
  /** Map each element of the structure to a [[scalaz.Monoid]], and combine the results. */
  def foldMap[A,B](fa: F[A])(f: A => B)(implicit F: Monoid[B]): B

  /**Right-associative fold of a structure. */
  def foldRight[A, B](fa: F[A], z: => B)(f: (A, => B) => B): B
}

Definition:

Laws:

//foldLeft where you append the value is equal to foldMap
foldMap(fa)(Vector(_)) == foldLeft(fa, Vector.empty[A])(_ :+ _)

//foldRight where you prepend the value is equal to foldMap 
foldMap(fa)(Vector(_)) == foldRight(fa, Vector.empty[A])(_ +: _)

Foldable

//Count how long the foldable structure is
def length[A](fa: F[A]): Int

//Convert to collections
def toList[A](fa: F[A]): List[A]
def toIndexedSeq[A](fa: F[A]): IndexedSeq[A]
def toSet[A](fa: F[A]): Set[A]

//Get Max, Min, etc
def minimumBy[A, B: Order](fa: F[A])(f: A => B): Option[A]
def maximumBy[A, B: Order](fa: F[A])(f: A => B): Option[A]

//foldMap when the value is a Monad.
def foldMapM[G[_], A, B](fa: F[A])(f: A => G[B])(implicit B: Monoid[B], G: Monad[G]): G[B]

//Plus *many* more

Gives Us:

Foldable

List(1,2,3,4).foldMap(identity)
//yields 10

//This is the same as 
List(1,2,3,4).foldLeft(Monoid[Int].zero){(acc,i) => acc |+| i}

Example:

Foldable

List(Map("A" -> List(1,2,3)), Map("A" -> List(4,5,6)), Map("B" -> List(1))).foldMap(identity)

// Yields List(Map("A" -> List(1,2,3,4,5,6), Map("B" -> List(1)))

Example:

Foldable

val listOfMaps = List(Map("A" -> 1), Map("A" -> 2), Map("B" -> 3))

//Compose a nested Foldable
Foldable[List].compose[Map[String,?]].foldMap(listOfMaps)(_ + 100)
//Result
306

Example:

Traverse


trait Traverse[F[_]] extends Functor[F] with Foldable[F] {
  /** Transform `fa` using `f`, collecting all the `G`s with `ap`. */
  def traverseImpl[G[_]: Applicative, A, B](fa: F[A])(f: A => G[B]): G[F[B]]
}

Definition:

Laws:

/**
 *
 * See Traverse.scala, it's verbose...
 *
 */

Traverse

/** A version of `traverse` that infers the type constructor `G`. */
def traverseU[A, GB](fa: F[A])(f: A => GB)(implicit G: Unapply[Applicative, GB]): G.M[F[G.A]]

def sequenceU[A](self: F[A])(implicit G: Unapply[Applicative, A]): G.M[F[G.A]]

/**The composition of Traverses `F` and `G`, `[x]F[G[x]]`, is a Traverse */
def compose[G[_]](implicit G0: Traverse[G]): Traverse[({type λ[α] = F[G[α]]})#λ]

//And more!

Gives Us:

Traverse

Traverse

Examples:

List(1,2,3).map(i => Option(i + 100)).sequenceU
// yields Some(List(101,102,103))

List(1,2,3).map(i => if(i == 1) None else Some(i)).sequenceU
// yields None



Traverse

Examples:

List(1,2,3).map(i => Option(i + 100)).sequenceU
// yields Some(List(101,102,103))

List(1,2,3).map(i => if(i == 1) None else Some(i)).sequenceU
// yields None



.map + sequenceU == traverseU

Traverse

List(1,2,3).traverseU(i => Option(i + 100))
// yields Some(List(101,102,103))

List(1,2,3).traverseU(i => if(i == 1) None else Some(i))
// yields None



Examples:

Traverse

type MyValidation[A] = Validation[NonEmptyList[String], A]

def validateUser(u: String): MyValidaton[User] = ...

List("coltfred", "bob", "%%%%%%%", "$$$$$").traverseU(validateUser(_))

// yields Failure(NonEmptyList("'%%%%%%%' isn't a valid user","'$$$$$' isn't a valid user"))



Examples:

Traverse

type MyValidation[A] = Validation[NonEmptyList[String], A]

def validateUser(u: String): MyValidaton[User] = ...

List("coltfred", "bob").traverseU(validateUser(_))

// yields Success(List(User("coltfred"), User("bob")))



Examples:

Thank you!

Questions?

Ask now or come to #scalaz IRC!

Scalaz 102

By Colt Frederickson

Scalaz 102

  • 3,034
Loading comments...

More from Colt Frederickson