7

I have 19 strings that need to be validated into various types. When all validate successfully, I would like to instantiate a class that represents a row of a spreadsheet (where the columns do not all have the same type).

When one or more of the strings fails to validate, I would like to have the errors accumulated in a NonEmptyList.

If there were 12 or fewer items, I could use |@| or apply12. If I use a for expression, it fails fast and no accumulation happens.

I could sequence the failures when the for expression fails, but that means I'm looping twice. Is there a way to use scalaz to pull each validation success into a variable (as would happen if i used a for expression to instantiate the class) at the same time as accumulating all of the failures?

Jim Hunziker
  • 14,111
  • 8
  • 58
  • 64
  • 1
    It's a little clunky, but you can [use `<*>` (or `ap`) directly](http://stackoverflow.com/a/11502894/334519), which will accumulate errors and doesn't have an arbitrary limit on the number of times it can be applied. – Travis Brown Jun 05 '13 at 05:15
  • Couldn't you just `map` the list of strings to `Validation` and then `partition` the resulting list by `isFailure`. – cmbaxter Jun 05 '13 at 08:52

1 Answers1

12

Suppose we have a case class (which could have more than twelve members):

case class Foo(a: Int, b: Char, c: Symbol, d: String)

And that we're representing errors as strings and have defined a type alias for convenience:

type ErrorOr[A] = ValidationNel[String, A]

We also have some validation results:

val goodA: ErrorOr[Int] = 1.success
val goodB: ErrorOr[Char] = 'a'.success
val goodC: ErrorOr[Symbol] = 'a.success
val goodD: ErrorOr[String] = "a".success

val badA:  ErrorOr[Int] = "x".failNel
val badC:  ErrorOr[Symbol] = "y".failNel

Now we can write:

val foo = (Foo.apply _).curried

val good: ErrorOr[Foo] = goodD <*> (goodC <*> (goodB <*> (goodA map foo)))
val bad:  ErrorOr[Foo] = goodD <*> (badC  <*> (goodB <*> (badA  map foo)))

Which gives us what we want:

scala> println(good)
Success(Foo(1,a,'a,a))

scala> println(bad)
Failure(NonEmptyList(x, y))

In Haskell this would be much prettier—you'd just write:

Foo <$> goodA <*> goodB <*> goodC <*> goodD

Scala's weaker type inference requires us to write the arguments in the wrong order, unfortunately.

Community
  • 1
  • 1
Travis Brown
  • 138,631
  • 12
  • 375
  • 680
  • This works for me, though it would be nice not to have a fleet of close parens. :) – Jim Hunziker Jun 05 '13 at 15:30
  • 2
    Note that I've written up [a blog post](http://meta.plasm.us/posts/2013/06/05/applicative-validation-syntax/) with some alternatives using [Shapeless](https://github.com/milessabin/shapeless). – Travis Brown Jun 06 '13 at 01:06