Reputation: 1761
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.
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)
}
This does what I expect, but I have some questions about the usage:
import scalaz._
somehow didn't work for me.[1] How can I figure this out from the API docs?[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
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