A result builder is a struct with the @resultBuilder attribute that implements one or more of the required functions for different syntax cases. The first version only contains buildBlock(_:...) that’s used to build a result from 1+ validations:
This is the simplest case where we return the first validation (a new test verifies that). To use this builder you need to have a function that accepts a closure containing the validation steps:
As I understand, validations can’t be empty in that buildBlock because there is another signature for the case when there is nothing in the result builder and I haven’t implemented that one, so the implementation was:
This is where we && all the supplied validations, returning the same result as if we manually &&‘ed them. V.empty = V.value(()) is an identity that can be combined with any validation and not change it.
The next functions I added are to support ifs without and with else:
/// Usernames are minimum 3 chars long and cannot include `@`.privatefuncvalidateUserName()->SimpleValidationResult{// for some reason, the strings can't be automatically converted to `StringError` here// `Cannot convert value of type 'V<(), String>' to closure result type 'V<(), StringError>'`SimpleValidationResult.all{userName.count>=3<?>StringError("Username \(userName) must be 3+ chars")!userName.contains("@")<?>StringError("Username \(userName) must not contain '@'")}}
Result
How much cleaner is the resulting syntax? With the simple example, not much honestly. But since you can also support conditions and loops right in the closures, it probably becomes more straightforward then.
I still can’t shake the feeling that adding these syntax sugars to the compiler is a limiting approach because no library can do the same. For example, throws added error reporting to functions, but it’s built into language and has shortcomings vs creating a Result (aka Either) that can be done by any library w/o touching the compiler.