A Result represents an operation that can either succeed with a value or fail with information about what went wrong. It is useful when absence alone is not enough: validation may reject input for a specific reason, and later code may need to choose a response based on that reason. In F#, Result is a bundled type for modeling these ordinary, expected outcomes as return values. section 9.0 section 9.2
Result has two cases: Ok for success and Error for failure. Both cases carry payloads, and their payload types may differ. A function with the shape RawCustomer -> Result<Customer, string>, for example, either produces a validated Customer or a string explaining why conversion failed. This preserves the successful value while making failure explicit in the type. section 9.2.1
Listing 9.2 Using results instead of options for validation
let validateCustomer (rawCustomer:RawCustomer) =
let customerId =
if rawCustomer.CustomerId.StartsWith "C" then
Ok (CustomerId(int rawCustomer.CustomerId[1..])) #1
else
Error $"Invalid Customer Id '{rawCustomer.CustomerId}'." #2
let country =
match rawCustomer.Country with
| "" -> Error "No country supplied" #2
| "USA" -> Ok Domestic #1
| other -> Ok (Foreign other) #1
match customerId, country with
| Ok customerId, Ok country -> #3
Ok ... // body omitted for clarity
| Error err, _ #4
| _, Error err ->
Error err
This validation function returns Ok values for valid customer fields and Error messages for invalid ones, then pattern matches on both results to construct a customer or return the encountered error. section 9.2.1
Use Result rather than Option when callers must know why an operation failed. Option models possible absence with Some or None; Result adds a payload-bearing error channel. Pattern matching handles the final success or failure decision, while map and bind let code transform or continue successful values without first removing them from the Result container. Result.mapError similarly transforms error values. section 9.2.1 section 9.2.6
Choose an error-handling approach by the kind of outcome being modeled:
- Use Option for data that may simply be absent, where no failure explanation is needed. section 9.2.6
- Use Result for pass-or-fail operations whose errors must be retained, inspected, or converted into a response. section 9.2.6
- Use exceptions for genuinely exceptional, fail-fast situations rather than regular control flow. section 9.2.6
A Result need not stop at one error. Set its error type to a collection such as string list to return several validation messages in one failure value. This makes the error channel part of the domain design: a simple string can be enough initially, while a custom discriminated union can model error cases more tightly when the application needs structured decisions. section 9.2.2 section 9.2.6
Validation at an HTTP boundary
Map raw JSON into a record of simple primitive values, then attempt to convert it into a rich domain type with a Result-returning validation function. Send validation messages from Error to an HTTP 4xx response; send the Ok value into application handling such as calculation or database writing, then return success. Reusable composable validation functions can replace a large validation framework for many cases. section 13.2.7







