Yann Moisan

Scala, Web, Linux…

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 Applicatives.

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 Monads.

To go further

Here is some features that are not used but could be useful.

  • tagged type to add type safety on Id (compositeIds could return List[Id @@ Composite]
  • use 'Reader' monad to manage store dependency

Links