Scala - Just Do It!
(Functionally)
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!
Scala Syntax Guide
val x = 1
var y = 100
y = y + 1 //101
val z: Int = y
Scala Syntax Guide
case class Tuple2[A,B](first: A, second: B)
val t: Tuple2[Int,String] = Tuple2(2, "hello")
//I could just say
val tt = Tuple2(2, "hello")
def plus1ToFirst[A](t: Tuple2[Int,A]) = t.copy(first = t.first + 1)
Algebraic Data Type
sealed trait Option[+A]
case class Some[A](a: A) extends Option[A]
case object None extends Option[Nothing]
Polymorphism
class HttpConnection {
def fetch(url:String): String
def close: Unit
}
class HBaseTable extends java.io.Closeable {
def get(row: String, col: String): String
def close: Unit
}
//I want to be able to write things like the following.
val result = using(new HttpConnection()){ connection =>
connection.fetch("http://google.com")
}
//or
val result = using(new HBaseTable()){ table =>
table.fetch("myrow", "mycol")
}
Polymorphism
def using[A,B](a: A)(f: A => B): Option[B] = {
val result = Try(f(a))
//TODO how can we close the resource A?
result.toOption
}
Polymorphism
def using[A <: java.io.Closeable,B](a: A)(f: A => B): Option[B] = {
val result = Try(f(a))
a.close()
result.toOption
}
Polymorphism
val result = using(new HttpConnection()){ connection =>
connection.fetch("http://google.com")
}
//error: inferred type arguments [HttpConnection,Nothing] do not
// conform to method using's type
// parameter bounds [A <: java.io.Closeable,B]
Polymorphism
Typeclasses
trait CanClose[A]{
def close(a: A): Unit
}
def using[A,B](a: A)(f: A => B)(canClose: CanClose[A]): Option[B] = {
val result = Try(f(a))
canClose.close(a)
result.toOption
}
Typeclasses
val closeableCanClose =
new CanClose[java.io.Closeable] {
def close(c: java.io.Closeable): Unit = {
c.close()
}
}
val result = using(new HBaseTable()){ table =>
table.fetch("myrow", "mycol")
}(closeableCanClose)
Typeclasses
def using[A,B](a: A)(f: A => B)(implicit canClose: CanClose[A]): Option[B] = {
val result = Try(f(a))
canClose.close(a)
result.toOption
}
implicit val closeableCanClose =
new CanClose[java.io.Closeable] {
def close(c: java.io.Closeable): Unit = {
c.close()
}
}
val result = using(new HBaseTable()){ table =>
table.fetch("myrow", "mycol")
}
Typeclasses
def using[A,B](a: A)(f: A => B)(implicit canClose: CanClose[A]): Option[B] = {
val result = Try(f(a))
canClose.close(a)
result.toOption
}
//Exactly the same as
def using[A: CanClose,B](a: A)(f: A => B): Option[B] = {
val result = Try(f(a))
implicitly[CanClose[A]].close(a)
result.toOption
}
Scalaz to the Rescue!
- Data Types
- IO
- Task
- \/ (Disjunction)
- NonEmptyList
- etc.
- Better abstractions (type classes)
- Functor
- Applicative
- Monad
- Foldable
- Traverse
- etc.
What is Scalaz?
What is Scalaz?
trait MonadTrans[F[_[_], _]] {
...
}
What is Scalaz?
type ->[A, B] = (({type λ[α]=A})#λ) ~> (({type λ[α]=B})#λ)
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
- \/ (Disjunction)
- Validation
- Typeclasses
- Semigroup
- Monoid
- Functor
- 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] (val head: A, val tail: List[A])
Definition:
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
Use the types! Don't be a jerk!
Disjunction
sealed trait \/[+A,+B]
//Left disjunction (commonly an error)
final case class -\/[+A](a: A) extends (A \/ Nothing)
//Right disjunction (the success value)
final case class \/-[+B](b: B) extends (Nothing \/ B)
Definition:
Disjunction
//Convert to an option throwing away the error
def toOption: Option[B]
/** Convert to a core `scala.Either` at your own peril. */
def toEither: Either[A, B]
/** Return the right value of this disjunction or the given default if left. Alias for `|` */
def getOrElse[BB >: B](x: => BB): BB
/** Map on the right of this disjunction. */
def map[D](g: B => D): (A \/ D)
/** Bind through the right of this disjunction. */
def flatMap[AA >: A, D](g: B => (AA \/ D)): (AA \/ D)
Operations:
Disjunction
type Error = Exception //Could be *anything*
def lookupEmployee(name:String): Error \/ Employee = ...
def lookupStartTime(id: Long): Error \/ Long = ...
def getEmployeeAndStart(name:String): Error \/ (Employee, DateTime) = ???
Example:
Disjunction
type Error = Exception //Could be *anything*
def lookupEmployee(name:String): Error \/ Employee = ...
def lookupStartTime(id: Long): Error \/ Long = ...
def getEmployeeAndStart(name:String): Error \/ (Employee, DateTime) = {
val employeeOrFail = lookupEmployee(name)
if(employeeOrFail.isRight) {
val employee = employeeOrFail.toOption.get
lookupStartTime(employee.id).map(time => employee -> Date(time))
} else {
//log error
}
}
// Oh god that's ugly (and might not even work)
Example:
Disjunction
type Error = Exception //Could be *anything*
def lookupEmployee(name:String): Error \/ Employee = ...
def lookupStartTime(id: Long): Error \/ Long = ...
def getEmployeeAndStart(name:String): Error \/ (Employee, DateTime) = {
lookupEmployee(name).flatMap { employee =>
lookupStartTime(employee.id).map { time =>
employee -> Date(time)
}
}
}
Example:
sealed trait \/[+A,+B]{
def flatMap[AA >: A, D](g: B => (AA \/ D)): (AA \/ D)
}
Disjunction
type Error = Exception //Could be *anything*
def lookupEmployee(name:String): Error \/ Employee = ...
def lookupStartTime(id: Long): Error \/ Long = ...
def getEmployeeAndStart(name:String): Error \/ (Employee, DateTime) =
for {
employee <- lookupEmployee(name)
time <- lookupStartTime(employee.id)
} yield employee -> Date(time)
Example:
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
/** Map on the success of this Validation */
def map[B](f: A => B): Validation[E, B]
//Many many more!
Operations:
Validation
- No .flatMap operation
- Accumulates errors
Notes
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:
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
Functor
trait Functor[F[_]] {
/** Lift `f` into `F` and apply to `F[A]`. */
def map[A, B](fa: F[A])(f: A => B): F[B]
}
Definition:
Laws:
map(fa)(x => x) == fa
/**
* A series of maps may be freely rewritten as a single map on a
* composed function.
*/
map(map(fa)(f1))(f2) == map(fa)(f2 compose f1))
Functor
List(1,2,3).map(_ + 1) //yields List(2,3,4)
Option("hello").map(_.reverse) //yields Some("olleh")
val compositeFunctor = Functor[List].compose(Functor[Option])
compositeFunctor.map(List(Option(1),None, Option(2))){_ + 1}
//yields List(Some(2), None, Some(3))
Examples:
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))
type MapWithStringKey[A] = Map[String,A]
//Compose a nested Foldable
Foldable[List].compose[MapWithStringKey].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!
Scala - Just Do It!
By Colt Frederickson
Scala - Just Do It!
Big Sky Dev Con 2015 talk.
- 1,889