如何用foldRight简化Scalaz ValidationNel多属性验证逻辑?
<*> with foldRight Absolutely! foldRight is perfect for cleaning up that messy nested <*> code—especially when you’re dealing with 15 validations. Let’s break down why your current approach is painful, how foldRight fixes it, and even add some extra polish for maintainability.
The Problem with Nested <*>
Your current code works, but with 15 validations, that nested structure would turn into an unreadable pyramid of _ => _ => ... lambdas. It’s error-prone to write, hard to debug, and a nightmare to modify if you need to add/remove validations later.
How foldRight Solves This
Since ValidationNel is an Applicative, <*> is right-associative—exactly the kind of operation foldRight is designed to handle. The key idea is to:
- Collect all your validation checks into a list.
- Use
foldRightto combine them sequentially, building up your final validated result without manual nesting.
Step-by-Step Implementation
First, let’s assume you have your validation functions defined like this (adjust error types and property types to match your code):
import scalaz._, Scalaz._ type ValidationResult[T] = ValidationNel[String, T] def validatePropertyA(a: String): ValidationResult[Unit] = if (a.nonEmpty) ().successNel else "Property A cannot be empty".failureNel def validatePropertyB(b: Int): ValidationResult[Unit] = if (b > 0) ().successNel else "Property B must be positive".failureNel // ... repeat for Properties C through O (13 more validations)
Next, gather all your validation calls into a list, passing in the relevant properties from your object:
case class MyObject(propertyA: String, propertyB: Int /* ... other properties */) val myObj = MyObject("test", 42 /* ... */) val validations: List[ValidationResult[Unit]] = List( validatePropertyA(myObj.propertyA), validatePropertyB(myObj.propertyB), // Add all 15 validation calls here )
Now use foldRight to combine these validations into a single result. The initial value is a successful validation of your object, and each step combines the next validation with the accumulated result:
val finalResult: ValidationResult[MyObject] = validations.foldRight(myObj.successNel) { (currentValidation, acc) => currentValidation.map(_ => identity[MyObject]) <*> acc }
Let’s unpack this:
myObj.successNelstarts us off with a valid object wrapped in a successfulValidationNel.- For each validation in the list, we
mapits successfulUnitresult to an identity function (since we don’t modify the object, just validate it). <*>combines this function-wrapped validation with the accumulated result: if both are successful, we keep the object; if any validation fails, all errors are collected into theNel(non-empty list) as expected withValidationNel.
Extra Polish: A Reusable Helper Function
To make this even cleaner, wrap the fold logic in a helper function so you can reuse it across your codebase:
def validateAll[T](obj: T, validations: List[ValidationResult[Unit]]): ValidationResult[T] = validations.foldRight(obj.successNel) { (v, acc) => v.map(_ => identity[T]) <*> acc } // Usage: val finalResult = validateAll(myObj, validations)
Is foldRight the Only Option?
While foldRight is the most straightforward fit here, you could also explore Applicative utilities like traverse or sequence if you wanted to collect all validation results first, but those would require extra steps to map back to your object. foldRight is the most direct solution because it mirrors the right-associative behavior of <*> without unnecessary overhead.
内容的提问来源于stack exchange,提问作者Hooberd

