Map and reduce/fold over HList of scalaz.Validation
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.
PS 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 Kleisli
s will have been "reduced" to Validation[String, A]
.
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 |@|
.
上一篇: 使用模块模式和实例化新对象之间的区别