arosien@box.com && adam@rosien.net @arosien #scalasv #scalaz


scalaz has a (undeserved?) reputation as being, well, kind of crazy.
So this talk is specifically not about:
This talk is about every-day situations where scalaz can:
In build.sbt:
| 1 2 | libraryDependencies += 
  "org.scalaz" %% "scalaz-core" % "6.0.4"
 | 
Then:
| 1 2 3 4 | import scalaz._
import Scalaz._
// profit
 | 
This is scalaz 6. Also, assume this is imported in all code snippets.
The goal: cache the result of an expensive computation.
| 1 2 3 4 5 6 7 | def expensive(foo: Foo): Bar = ...
val f: Foo
expensive(f) // $$$
expensive(f) // $$$
...
 | 
Assumption: expensive produces the same output for every input, i.e., is referentially-transparent.
Typically you might use a mutable.Map to cache results:
| 1 2 3 4 | val cache = collection.mutable.Map[Foo, Bar]()
cache.getOrElseUpdate(f, expensive(f)) // $$$
cache.getOrElseUpdate(f, expensive(f)) // 1¢
 | 
Downsides: the cache is not the same type as the function: Foo => Bar vs. Map[Foo, Bar]. It's also not DRY.
You can try to make it look like a regular function, avoiding the getOrElseUpdate() call:
| 1 2 3 4 5 6 | val cache: Foo => Bar = 
  collection.mutable.Map[Foo, Bar]()
    .withDefault(expensive _)
cache(f) // $$$ (miss & NO fill)
cache(f) // $$$ (miss & NO fill)
 | 
But it doesn't actually cache.
In scalaz:
| 1 2 3 4 5 6 7 8 9 10 11 | def expensive(foo: Foo): Bar = ...
// Memo[Foo, Bar]
val memo = immutableHashMapMemo { 
  foo: Foo => expensive(foo) 
}
val f: Foo
memo(f) // $$$ (cache miss & fill)
memo(f) // 1¢  (cache hit)
 | 
Many memoization strategies:
| 1 2 3 4 5 6 7 8 9 | immutableHashMapMemo[K, V]
mutableHashMapMemo[K, V]
// remove + gc unreferenced entries
weakHashMapMemo[K, V]
// fixed size, K = Int
arrayMemo[V](size: Int)
 | 
Super-nerdy: the memoizing strategies are just functions of K => V, which means the generic memo() constructor has the same signature as the Y-combinator!
scalaz memoization:
Remove the need for temporary variables:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 | val f: A => B
val g: B => C
// using temps:
val a: A = ...
val b = f(a)
val c = g(b)
// or via composition, which is a bit ugly:
val c = g(f(a))
// "unix-pipey"!
val c = a |> f |> g
 | 
When you just can't stand all that (keyboard) typing:
| 1 2 3 4 5 6 7 8 | val p: Boolean
// ternary-operator-ish
p ? "yes" | "no" // if (p) "yes" else "no"
val o: Option[String]
o | "meh"        // o.getOrElse("meh")
 | 
More legible (and more type-safe):
| 1 2 3 4 5 6 7 8 | // scala
Some("foo")  // Some[String]
None         // None.type
// scalaz
"foo".some   // Option[String]
none         // Option[Nothing], oops!
none[String] // Option[String]
 | 
More legible (and more type-safe):
| 1 2 3 4 5 6 7 8 9 | // scala
Right(42)   // Right[Nothing, Int], oops!
Left("meh") // Left[String, Nothing], oops!
Right[String, Int](42)   // verbose
Left[String, Int]("meh") // verbose
// scalaz
42.right[String] // Either[String, Int]
"meh".left[Int]  // Either[String, Int]
 | 
Pros: less noise, more expressive, more type-safe
Cons: you have to know these operators
This isn't good:
| 1 2 3 4 5 6 7 | case class SSN(
  first3: Int, 
  second2: Int, 
  third4: Int)
SSN(123, 123, 1234) 
//       ^^^ noo!
 | 
This shouldn't be possible:
| 1 2 3 4 | case class Version(major: Int, minor: Int)
Version(1, -1) 
//         ^^ noooo!
 | 
Meh:
| 1 2 3 4 5 6 7 | case class Dependency(
  organization: String,
  artifactId: String,
  version: Version)
Dependency("zerb", "", Version(1, 2))
//                 ^^ nooooo!
 | 

The problem is that the types as-is aren't really accurate.
Strings and Ints are being used too broadly.
We really want "Ints greater than zero", 
"Strings that match a pattern", etc.
You can do the checks in the constructor:
| 1 2 3 4 5 6 7 8 | case class Version(major: Int, minor: Int) {
  require(
    major >= 0, 
    "major must be >= 0: %s".format(major))
  require(
    minor >= 0, 
    "minor must be >= 0: %s".format(minor))
}
 | 
But this has downsides:
| 1 2 3 4 5 6 7 8 9 10 11 | val major: Int = ...
val minor: Int = ...
val version: ??? =
  Version.validate(major, minor)
version | Version(1, 0) // provide default
// handle failure and success
version.fold(
  fail: ???        => ...,
  success: Version => ...)
 | 
Using scalaz, a Validation can either be a Success or Failure:
| 1 2 3 4 5 | Version.validate(1, 2)
// Success(Version(1, 2))
Version.validate(1, -1)
// Failure(NonEmptyList("digit must be >= 0"))
 | 
Model the >= 0 constraint:
| 1 2 3 4 5 6 7 8 9 10 11 12 | case class Version(
  major: Int, // >= 0
  minor: Int) // >= 0
object Version {
  def validDigit(digit: Int):
    Validation[String, Int] = (digit >= 0) ? 
      digit.success[String] | 
      "digit must be >= 0".fail
  ...
}
 | 
Combine constraints:
| 1 2 3 4 5 6 7 8 9 10 | object Version {
  def validDigit(digit: Int): 
    Validation[String, Int] = ...
  def validate(major: Int, minor: Int) = 
    (validDigit(major).liftFailNel |@| // huh?
     validDigit(minor).liftFailNel) {  // huh?
      Version(_, _)                    // huh?
    }
}
 | 
Let's break down validDigit(major).liftFailNel:
| 1 2 3 4 5 6 7 8 9 10 11 | validDigit(major)
// Validation[String, Int]
validDigit(major).liftFailNel
//   lift = do stuff inside
//   fail = only work on the failure side
//   nel  = NonEmptyList
// Validation[NonEmptyList[String], Int]
type ValidationNEL[X, A] =
  Validation[NonEmptyList[X], A]
 | 
| 1 2 3 4 5 6 7 8 9 10 11 | val maj = validDigit(major).liftFailNel
val min = validDigit(minor).liftFailNel
// Both ValidationNEL[String, Int]
//                            ^^^
val mkVersion = Version(_, _)
// (Int, Int) => Version
val version = (maj |@| min) { mkVersion }
// ValidationNEL[String, Version]
//                       ^^^^^^^
 | 
The general form of combining ValidationNEL:
| 1 2 3 4 5 6 7 8 9 10 11 12 | (ValidationNEL[X, A] |@|
 ValidationNEL[X, B]) {
  (A, B) => C
} // ValidationNEL[X, C]
(ValidationNEL[X, A] |@|
 ValidationNEL[X, B] |@|
 ValidationNEL[X, C]) {
  (A, B, C) => D
} // ValidationNEL[X, D]
// etc.
 | 
The "rules":
| 1 2 3 4 5 6 7 8 9 10 11 | Success |@| Success // Success
Success |@| Failure // Failure
Failure |@| Success // Failure
Failure |@| Failure // Failure 
// and accumulate fail values!
// Accumulate?
NonEmptyList("foo") |+| NonEmptyList("bar")
// NonEmptyList("foo", "bar")
// |+|? "appends" things according to rules
 | 
An improvement?
Validation/Success/Failure is nicer than try/catch or Either/Left/Right.Validation.liftFailNel, |@|, etc., is incomprehensible if you're not familiar.Overall:

Let's say you have some nested structure like a tree:
| 1 2 3 4 5 6 7 | // the data
case class Foo(name: String, factor: Int)
// a node of the tree
case class FooNode(
  value: Foo, 
  children: Seq[FooNode] = Seq())
 | 
Make a tree of Foo's:
| 1 2 3 4 5 6 | val tree = 
  FooNode(
    Foo("root", 11),
    Seq(
      FooNode(Foo("child1", 1)),
      FooNode(Foo("child2", 2)))) // <-- * 4
 | 
Task: Create a new tree where the second child's factor is multiplied by 4.
Let's try all at once:
| 1 2 3 4 5 6 7 8 9 | val secondTimes4: FooNode => FooNode = 
  node => node.copy(children = {
    val second = node.children(1)
    node.children.updated(
      1, 
      second.copy(
        value = second.value.copy(
          factor = second.value.factor * 4)))
  })
 | 
Eww: temporary variables, x.copy(field = f(x.field)) boilerplate, deep nesting for every level.
Wouldn't it be better to have one thing to address something in a Foo or FooNode, and just combine them?
| 1 2 3 4 5 6 7 8 9 10 | val second = // second node child
val value  = // value of a node
val factor = // factor field of Foo
val secondFactor = second ??? value ??? factor
secondFactor(tree) // get 2
secondFactor.set(tree, 8)     // set 8 there
secondFactor.mod(tree, _ * 4) // modify there
 | 
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | val second: Lens[FooNode, FooNode] = 
  Lens(
    _.children(1),
    (node, c2) => node.copy(
      children = node.children.updated(1, c2)))
val value: Lens[FooNode, Foo] = 
  Lens(
    _.value,
    (node, value) => node.copy(value = value))
val factor: Lens[Foo, Int] = 
  Lens(
    _.factor, 
    (foo, fac) => foo.copy(factor = fac))
 | 
The Lens type encapsulates "getters" and "setters" on another type.
| 1 2 3 4 5 6 7 8 9 10 11 | Lens[Thing, View](
  get: Thing => View,
  set: (Thing, View) => Thing)
val thing: Thing
val lens:  Lens[Thing, View] = ...
val view: View = lens(thing) // apply = get
// "set" a view
val thing2: Thing = lens.set(thing, view)
 | 
Lenses compose:
| 1 2 | val secondFactor = 
  second andThen value andThen factor
 | 
| 1 2 3 4 5 6 7 8 9 | /* FooNode(
     Foo("root", 11),
     Seq(
       FooNode(Foo("child1", 1)),
       FooNode(Foo("child2", 2))))
                             ^
                             ^
                             ^  */
secondFactor(tree)        // 2
 | 
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | /* FooNode(
     Foo("root", 11),
     Seq(
       FooNode(Foo("child1", 1)),
       FooNode(Foo("child2", 2))))
                             ^
                             ^  */
secondFactor.mod(tree,   _ * 4)
/* FooNode(                  ^
     Foo("root", 11),        ^
     Seq(                    ^
       FooNode(Foo("child1", ^)),
       FooNode(Foo("child2", 8))))
 */
 | 
scalaz for "deep" access:
arosien@box.com && adam@rosien.net @arosien #scalasv #scalaz
Thank the scalaz authors: runarorama, retronym, tmorris and lots others.
Credits, sources and references:
| Table of Contents | t | 
|---|---|
| Exposé | ESC | 
| Full screen slides | e | 
| Presenter View | p | 
| Source Files | s | 
| Slide Numbers | n | 
| Toggle screen blanking | b | 
| Show/hide slide context | c | 
| Notes | 2 | 
| Help | h |