Map and reduce/fold over HList of scalaz.Validation
Asked Answered
B

1

7

I started out with something like this:

def nonEmpty[A] = (msg: String) => (a: Option[A]) => a.toSuccess(msg)

val postal: Option[String] = request.param("postal")
val country: Option[String] = request.param("country")

val params =
  (postal  |> nonEmpty[String]("no postal" )).toValidationNel |@|
  (country |> nonEmpty[String]("no country")).toValidationNel

params { (postal, country) => ... }

Now I thought it would be nice to reduce the boilerplate for better readability and for not having to explain to more junior team members what .toValidateNel and |@| mean. The first thought was List but then the last line would stop working and I'd have to give up some static safety. So I looked towards Shapeless:

import shapeless._; import poly._; import syntax.std.tuple._

val params = (
  postal  |> nonEmpty[String]("no postal"),
  country |> nonEmpty[String]("no country")
)

params.map(_.toValidatioNel).reduce(_ |@| _)

however, I can't even seem to get past the .map(...) bit. I've tried as per a suggestion on #scalaz:

type Va[+A] = Validation[String, A]
type VaNel[+A] = ValidationNel[String, A]

params.map(new (Va ~> VaNel) { def apply[T](x: Va[T]) = x.toValidationNel })

...to no avail.

I've asked for help on #scalaz but it doesn't seem something people just have an out of the box answer to. However, I'm really keen on learning how to solve this both for practical as well as learning purposes.

P.S. in reality my validations are wrapped inside Kleisli[Va, A, B] so that I could compose individual validation steps using >=> but that seems to be orthogonal to the issue as by the time that .map(...) is reached, all Kleislis will have been "reduced" to Validation[String, A].

Bilbrey answered 16/10, 2014 at 17:9 Comment(2)
You'll definitely need to define the Poly1 as an object (for various wacky Scala-related reasons having to do with stable identifiers). This looks a lot like a traversal, and shapeless-contrib's traverse would let you do the toValidationNel and (the moral equivalent of) the reduce(_ |@| _) in one step.Vassell
See also my vaguely related blog post here.Vassell
V
3

Here's what this would look like with shapeless-contrib's traverse:

import scalaz._, Scalaz._
import shapeless._, contrib.scalaz._, syntax.std.tuple._

def nonEmpty[A] = (msg: String) => (a: Option[A]) => a.toSuccess(msg)

val postal: Option[String] = Some("00000")
val country: Option[String] = Some("us")

val params = (
  postal  |> nonEmpty[String]("no postal"),
  country |> nonEmpty[String]("no country")
)

And then:

object ToVNS extends Poly1 {
  implicit def validation[T] = at[Validation[String, T]](_.toValidationNel)
}

val result = traverse(params.productElements)(ToVNS).map(_.tupled)

Now result is a ValidationNel[String, (String, String)], and you can do anything with it that you could do with the awful ApplicativeBuilder thing you'd get from reducing with |@|.

Vassell answered 16/10, 2014 at 23:21 Comment(6)
could you elaborate a little bit on why and how traverse is different from just a regular map, why it works and map doesn't, and why it's not (yet) part of Shapeless?Bilbrey
@TravisBrown Why do we need the .productElements and .map(_.tupled)? Shouldn't shapeless's Generic support for tuples allow us to traverse the tuple directly? @ErikAllik traverse is like a map that accumulates Applicative effects. If we were to use regular map we'd get a (ValidationNel[String, String], ValidationNel[String, String]); the traverse also "sequences" the ValidationNel effect so we have a single ValidationNel for the overall tuple. traverse depends on scalaz, so it can't be part of main Shapeless as they don't want to introduce that dependency.Azoth
Travis: any pointers to why ApplicativeBuilder is awful (e.g. vs just a plain ValidationNel) would be greatly appreciated!Bilbrey
@Erik: the problem with ApplicativeBuilder is that it's just a syntax hack—it doesn't mean anything. It's essentially a parameter list waiting for a function that it can lift into the applicative functor and apply to itself.Vassell
So it's bad because syntax hacks are bad in general, or it's just that this particular hack is bad? Or just unneeded?Bilbrey
I guess "awful" is a little strong, but contrast with Haskell, where you just write foo <$> a <*> b <*> c (function and arguments in the usual order, no intermediate Builder things, etc.).Vassell

© 2022 - 2024 — McMap. All rights reserved.