Scalaz from the trenches
Scalaz
The aim of this page is to show the use of Scalaz on the project. Scalaz defines itself as an extension to the core Scala library for functional programming
option
Scalaz pimp your options.
scala> 1.some.toSuccess("error")
res32: scalaz.Validation[String,Int] = Success(1)
scala> None.toSuccess("error")
res33: scalaz.Validation[String,A] = Failure(error)
sequence
Scalaz API is hard to read :
trait TraverseOps[F[_], A] extends scala.AnyRef with scalaz.syntax.Ops[F[A]] {
final def sequence[G[_], B](implicit ev : scalaz.Leibniz.===[A, G[B]], G : scalaz.Applicative[G]) : G[F[B]] = { /* compiled code */ }
}
Here is some explanation from Scalaz Traverse examples :
// An easy to understand first step in using the Traverse typeclass
// is the sequence operation, which given a Traverse[F] and
// Applicative[G] turns F[G[A]] into G[F[A]]. This is like "turning
// the structure 'inside-out'":
So, it allows to do simple things :
scala> List(Option(1), Option(2)).sequence
res8: Option[List[Int]] = Some(List(1, 2))
scala> List(Option(1), None).sequence
res9: Option[List[Int]] = None
// The effect of the inner Applicative is used, so in the case of
// the Option applicative, if any of the values are None instead of
// Some, the result of the entire computation is None:
Obviously, it can be done without Scalaz by implementing a sequence
method :
def sequence[A](l: List[Option[A]]): Option[List[A]] =
l.foldRight(Some(Nil: List[A]):Option[List[A]])((oa, ola) => oa.flatMap(a => ola.map(la => a :: la)))
But it seems like reinventing the wheel, and it will only work for Option
, and not for all
Applicative
s.
Validation
In scala, we use Either
as a return type to represent a method that can fail. Unfortunately, the
type Either
has some limitations and doesn’t allow to collect a list of errors. Let’s introduce
new datatypes Validation
to resolve this issue :
scala> def f = { x : String => if (!x.toLowerCase.equals(x)) "only lowercase allowed".failure[String] else "ok2".success }
f: String => scalaz.Validation[String,String]
scala> def g = { x : String => if (x.contains(" ")) "no space allowed".failure[String] else "ok1".success[String] }
g: String => scalaz.Validation[String,String]
scala> (f("A b") |@| g("A b")) {_ + _}
res28: scalaz.Unapply[scalaz.Apply,scalaz.Validation[String,String]]{type M[X] = scalaz.Validation[String,X]; type A = String}#M[String] = Failure(only lowercase allowedno space allowed)
Here is some explanation from Scalaz Apply examples :
// |@| is refered to as "applicative builder", it allows you to
// evaluate a function of multiple arguments in a context, similar
// to apply2, apply3, apply4, etc:
Kleisli
It allows to combine monadic functions
scala> def f = { x : Int => Option(x+1) }
f: Int => Option[Int]
scala> def g = { x : Int => Option(x*2) }
g: Int => Option[Int]
scala> Kleisli(f) >=> Kleisli(g)
res10: scalaz.Kleisli[Option,Int,Int] = scalaz.KleisliFunctions$$anon$17@6442084f
scala> res10(3)
res11: Option[Int] = Some(8)
Obviously, it can also be done without Scalaz, by implementing a compose
method :
scala> def compose[A,B,C](f: A => Option[B], g: B => Option[C]) : A => Option[C] = a => f(a).flatMap(g)
compose: [A, B, C](f: A => Option[B], g: B => Option[C])A => Option[C]
scala> compose(f,g)(3)
res1: Option[Int] = Some(8)
But it seems like reinventing the wheel, and it will only work for Option
, and not for all
Monad
s.
To go further
Here are some features that are not used but could be useful.
- tagged type to add type safety on
Id
(compositeIds could returnList[Id @@ Composite]
- use ‘Reader’ monad to manage store dependency