Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Validation usage with |@| in Scalaz

Tags:

scala

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:

  • Is there an easy to use alternative to scalaz for this use-case? I'd prefer to not open pandora's box and introduce scalaz if I don't have to.
  • One reason I'd like to not use scalaz is that it's really really hard to figure out what to do if you don't, like me, know the entire framework. For example, what is the list of implicits that you need to get the above code to work? import scalaz._ somehow didn't work for me.[1] How can I figure this out from the API docs?
  • Is there a more succinct way to express the validation use-case? I stumbled my way through until I arrived at something that worked and I have no idea if there are other, better ways of doing the same thing in scalaz.

[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
like image 405
Chris Scott Avatar asked Sep 02 '15 16:09

Chris Scott


1 Answers

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.

like image 119
Travis Brown Avatar answered Nov 06 '22 07:11

Travis Brown