Validation usage with |@| in Scalaz
Asked Answered
C

1

6

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
Clevelandclevenger answered 2/9, 2015 at 16:45 Comment(6)
"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" - I wouldn't say that directly to Tony Morris ;). Scalaz may not be everybody's cup of tea; there is a steep learning curve involved to understand it if you are not of the hardcore FP persuasion and/or not familiar with Haskell (it takes a lot of inspiration from that language). It's a powerful library if you know what to do with it - I get the impression you are knocking it because you can't be bothered to learn it fully - that's not Scalaz's faultZacynthus
An alternative to Scalaz is Cats. One of it's aims is to be more open and user friendly than Scalaz in terms of documentation and examples, although I have found the Scalaz community ready to help with any issues I have had.Zacynthus
Yes, a coworker recommended Cats but at version 0.2, is it ready for people to generally use it?Clevelandclevenger
Also, apologizes to any scalaz proponents if this post comes of as snarky. I really want to like it. I see the power. It's just that it is frustrating to get started. If there are any other tips that people had, I'd like to hear them. I genuinely want to know if there was an easier way to figure out what I was trying to figure out.Clevelandclevenger
I created github.com/knutwalker/validation for the reason of not wanting to open the gate to scalaz, it's just the Validation partMetic
The magic I was missed from the imports is to use import scalaz._, Scalaz._. I tried little-s scalaz and then when that didn't fix everything, I tried big-s scalaz by itself thinking it was a package. When that didn't work I went on an import hunt. More information on imports with scalaz here: eed3si9n.com/learning-scalaz/day13.htmlClevelandclevenger
P
13

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.

Pumpkin answered 2/9, 2015 at 20:42 Comment(2)
Just noticed that this isn't really an answer to your first question, but for now there's no right-biased, error-accumulating disjunction type in the standard library, so unless you want to roll your own, cats or Scalaz (or Scalactic) is your best bet.Pumpkin
And you answered a question that I had without me asking it, which is the use of the non-empty list classes (ValidationNel, etc). This is to avoid a simple concatenation of multiple error messages for anybody following along. Thanks, this was quite helpful.Clevelandclevenger

© 2022 - 2024 — McMap. All rights reserved.