One model from predicate checks to task execution
A single model for Result-based programs in F#.
Write predicate checks once. Keep fail-fast logic in
Result, accumulate multiple failures with Validation, then lift the same logic into Flow when the boundary needs environment access, async work, task interop, cancellation, or runtime policy.
type RegistrationError =
| EmailMissing
| UserNotFound
let validateEmail (email: string) : Result<string, RegistrationError> =
email
|> Check.notBlank
|> Check.orError EmailMissing
type User =
{ Email: string }
type Api =
{ LoadUser: int -> Task<Result<User, RegistrationError>> }
type Clock =
{ UtcNow: DateTimeOffset }
let readVerifiedEmail userId =
flow {
let! user = Env<Api> (_.LoadUser userId)
let! checkedAt = Env<Clock> _.UtcNow
let! email = validateEmail user.Email
return email, checkedAt
}