Chris Scott
Chris Scott

Reputation: 1761

Validation usage with |@| in Scalaz

Background

I have Map[String,String] of configuration values. I want to extract a series of keys and provide meaningful error messages if any of them are missing. For example:

val a = Map("url"->"http://example.com", "user"->"bob", "password"->"12345")

Say I want to transform this into a case class:

case class HttpConnectionParams(url:String, user:String, password: String)

Now, I can simply use a for loop to extract the values:

for(url <- a.get("url"); 
    user <- a.get("user"); 
    password <- a.get("password")) yield {  
  HttpConnectionParams(url,user,password) 
}

To get an Option[HttpConnectionParams]. This is nice and clean, except if I get a None then I don't know what was missing. I'd like to provide that information.

Validation with Scalaz

Enter scalaz. I'm using version 7.1.3.

From what I've been able to put together (a good reference is here) I can use disjunctions:

for(url <- a.get("url") \/> "Url must be supplied"; 
    user <- a.get("user") \/> "Username must be supplied"; 
    password <- a.get("password") \/> "Password must be supplied") yield {  
  HttpConnectionParams(url,user,password) 
}

This is nice because now I get an error message, but this is railway oriented because it stops at the first failure. What if I want to get all of the errors? Let's use validation and the applicative builder (aka "|@|"):

val result = a.get("url").toSuccess("Url must be supplied")    |@|
             a.get("username").toSuccess("Username must be supplied") |@|
             a.get("password").toSuccess("Password must be supplied")

result.tupled match {
  case Success((url,user,password)) => HttpConnectionParams(url,user,password)
  case Failure(m) => println("There was a failure"+m)
}

Questions

This does what I expect, but I have some questions about the usage:

[1] After much consternation I arrived at this set of imports for the applicative use-case. Hopefully this helps somebody:

import scalaz.std.string._
import scalaz.syntax.std.option._
import scalaz.syntax.apply._
import scalaz.Success
import scalaz.Failure

Upvotes: 6

Views: 597

Answers (1)

Travis Brown
Travis Brown

Reputation: 139038

You can do this a little more nicely by defining a helper method and skipping the .tupled step by using .apply:

import scalaz._, Scalaz._

def lookup[K, V](m: Map[K, V], k: K, message: String): ValidationNel[String, V] =
  m.get(k).toSuccess(NonEmptyList(message))

val validated: ValidationNel[String, HttpConnectionParams] = (
  lookup(a, "url", "Url must be supplied") |@|
  lookup(a, "username", "Username must be supplied") |@|
  lookup(a, "password", "Password must be supplied")
)(HttpConnectionParams.apply)

Also, please don't be ashamed to use import scalaz._, Scalaz._. We all do it and it's just fine in the vast majority of cases. You can always go back and refine your imports later. I also still stand by this answer I wrote years ago—you shouldn't feel like you need to have a comprehensive understanding of Scalaz (or cats) in order to be able to use pieces of it effectively.

Upvotes: 13

Related Questions