Category Theory
Kinds
trait Monoid[A] {
def zero: A
def append(a: A, b: => A): A
}a monad is a monoid in the category of endofunctors. What's the problem?" -- One Div Zero
Has point: A => M[A]
Is a functor, so has map: (A => B) => F[A] => F[B]
Can be viewed from two (related) perspectives
How to think about monadic action
map: (A => B) => M[A] => M[B]
join: M[M[A]] => M[A]
flatMap: map andThen join
What the hell is a "Free Monad" (and why do we care)?
for {
x <- 3.some
y <- 2.some
} yield {
if (x == 3) "Horse" else "Cow"
}import scalaz.syntax.id._
def strim(s: String): String = s.trim
def isCap(s: String): Boolean = s.capitalize == s
// Pipelining.
" Hi" |> strim |> isCap // true
" Hi" |> {_.trim} |> {s => s.capitalize == s} // true
// Kestrel combinator. Used for side-effects. Returns input.
val conn = OldLibrary.conn() <| { c =>
c.permissive()
c.ttl(2000L)
}
// getOrElse for null
val foo1 = "foo"
val foo2: String = null
foo1 ?? "bar" // "foo"
foo2 ?? "bar" // "bar"Fun with Id[A]
import scalaz.syntax.std.boolean._
true option 42 // Some(42)
false option 42 // None: Option[Int]
// Pull in defs not exposed by syntax
import scalaz.std.boolean._
test(2 > 1) // 1
test(1 > 2) // 0
// Pull in Monoid, Enum, Show for basic Scala types
import scalaz.std.anyVal._
true ?? 42 // 42 Needs Monoid[Int]
false ?? 42 // 0
Fun with Boolean
import scalaz.syntax.std.option._
// Note that Some(3) in vanilla Scala is Some[Int]
3.some // Some(3): Option[Int]
import scalaz.std.option._
// compare None which is None.type
none[Int] // None: Option[Int]
3.some.orZero // error - we need our Monoids
import scalaz.std.anyVal._
3.some.orZero // 3
none[Int].orZero // 0
// Note that we've also imported
//
// 3.some | 42 // 3
// none[Int] | 42 // 42
//
// but don't use this in SpotRight.
// Prefer .getOrElse and .foldFun with Option
Fun with Monoid[A]
import scalaz.std.anyVal._
import scalaz.syntax.semigroup._
List(1,2,3,4,5).foldLeft(0){_ |+| _} // 15. duh
// Tuple is a Monoid if elements are.
import scalaz.std.tuple._
List(1,2,3,4,5).view.map{(_,1)}.reduce{_ |+| _} // (15,5)
// Map[A,B: Monoid] is a Monoid
import scalaz.std.map._
Map("a" -> 1, "b" -> 2) |+| Map("b" -> 1, "c" -> 2)
// Grab instances for List and Set
import scalaz.std.list._
import scalaz.std.set._
val words = List(
"the", "quick", "sly", "fox", "jumped",
"over", "the", "lazy", "brown", "dog"
)
words.view
.map{w => Map(w.length -> List(w)}
.reduce{_ |+| _} // words collected by length
words.view
.map{w => Map(w.length -> Set(w)}
.reduce {_ |+| _} // unique words by lengthFun with Apply (Applicative)
import scalaz.Apply
import scalaz.std.option._
// Apply is Applicative without point()
// Another way to say that is it allows a different
// way to apply pure functions to effectful values
val env = Map('a -> 1, 'b -> 2, 'c -> 3)
implicit class SymV(val s: Symbol) extends AnyVal {
def v: Option[Int] = env.get(s)
}
def add2(x: Int, y: Int): Int = x + y
def add3(x: Int, y: Int, z: Int): Int = x + y + z
Apply[Option].apply2('a.v, 'b.v)(add2 _) // Some(3)
Apply[Option].apply3('a.v, 'b.v, 'c.v)(add3 _) // Some(6)
Apply[Option].apply2('a.v, 'e.v)(add2 _) // None
More fun with Apply (Applicative)
// How about something useful?
import scalaz.Apply
import scalaz.ValidationNel
import scalaz.std.option._
import scalaz.syntax.std.option._
import scalaz.syntax.nel._
case class Person(name: String, age: Int, children: List[String])
type VNel[A] = ValidationNel[String, A]
// Real call is with `key`. `value` and `fail` are allow us to pretend.
def iocall[A](key: String, value: A, fail: Boolean = false): Option[A] =
if (fail) none[A] else value.some
def iovnel[A](key: String, value: A, fail: Boolean = false): VNel[A] =
iocall(key, value, fail).toSuccess(s"failed to get key 'key'".wrapNel)
def iovnel2[A](key: String, value: A, fail: Boolean = false): Vnel[A] =
if (fail) s"failed to get key '$key'".failureNel[A] else value.successNel[String]
val result =
Apply[VNel].apply3(
iovnel("name", "JohnQ"),
iovnel("age", 32),
iovnel("children", List("Jack", "Jill"))
)(Person).fold(
fail = {nel => sys.error(s"Errors:\n ${nel.list.mkString("\n ")}")},
succ = identity
)
More fun with Applicative
// Motivation. Many functions taking same input (i.e., Reader)
import scalaz.syntax.traverse._ // Applicative traverseal
import scalaz.std.function._
import scalaz.std.list._
def plus1(x: Int): Int = x + 1
def double(x: Int): Int = x * 2
def toTen(x: Int): Int = (x % 10) + 10
val funs = List(plus1 _, double _, toTen _)
// List of Reader to Reader of List
val joined = funs.sequenceU
joined(3) // List(4, 6, 13)
// sequenceU helps Scala figure out Int => Int === (Int => A): Applicative
// Try it if using just sequence complains about the shape
// Traverse applies some transform to each List element and then does
// sequence. Compare to mapping then accumulating for a List.
val tjoined = funs.traverseU{ r => r.map{_ * 2} }
tjoined(3) // List(8, 12, 26)
import scalaz.Id.Id // grab Id (type Id[+X] = X)
funs.traverse { f => (f(3) + 1): Id[Int] } // List(5, 7, 14)Fun with Monad
// This sort of code should be somewhat familiar
import scalaz.MonadPlus
import scalaz.std.list._
import scalaz.std.option._
case class Person(name: String, age: Int, children: List[String])
def name2Person: Map[String,Person] = ???
def perFm[M[_],A](name: String, f: Person => M[A])(implicit M: MonadPlus[M]): M[A] =
( for {
rec <- name2Person.get(name)
} yield f(rec)
).getOrElse(M.empty[A])
def kidsAges(names: List[String]): List[Int] =
for {
parent <- names
child <- perFm(parent, _.children)
age <- perFm(child, r => List(r.age))
} yield age
val ages = kidsAges(List("Arnold", "Becky", "Charles", "Debra")
Other fun things
import scalaz.{ValidationNel,Apply}
import scalaz.syntax.validation._
type ValNel[A] = ValidationNel[String,A]
def get(path: String): ValNel[String] =
Play.current.configuration.getString(path)
.map(_.successNel[String])
.getOrElse(path.failureNel[String])
def getOr(path: String, default: => String): ValNel[String] =
get(path) orElse default.successNel[String]
// using ap6 instead of apply6 would expect a `Tuple6 => R`
Apply[ValNel].apply6(
get("slick.db.driver"),
get("db.default.driver"),
get("db.default.url"),
getOr("db.default.user", ""),
getOr("db.default.password", ""),
Some(play.api.db.DB.getDataSource()).successNel[String]
)(DBConf.apply).fold(
fail = {ps => sys.error(s"Required config value not found: ${ps.list.mkString(", ")}")},
succ = AppDB.createDAL
)