Skip to content

aivi.validation

Utilities for working with Validation E A — like Result, but with an accumulation-oriented applicative path for independent failures. The current executable accumulation surface is zipValidation, which combines two Validation (NonEmptyList E) values and collects errors from both sides.

When you want the ambient prelude surface, these same operations are re-exported as isValid, isInvalid, validationGetOrElse, validationMapErr, validationToResult, validationFromResult, validationToOption, validationMap, validationAndThen, zipValidation, and validationFold.

aivi
use aivi.validation (
    Errors
    isValid
    isInvalid
    getOrElse
    mapErr
    toResult
    fromResult
    toOption
    map
    andThen
    zipValidation
    fold
)

At a glance

ExportTypeUse it for
Errors ENonEmptyList ECanonical accumulated error carrier
isValidValidation E A -> BoolCheck whether validation succeeded
isInvalidValidation E A -> BoolCheck whether validation failed
getOrElseA -> Validation E A -> AUnwrap with a fallback value
mapErr(E1 -> E2) -> Validation E1 A -> Validation E2 ATransform only the error side
toResultValidation E A -> Result E AConvert to Result
fromResultResult E A -> Validation E AConvert from Result
toOptionValidation E A -> Option ADiscard errors and keep the value when valid
map(A -> B) -> Validation E A -> Validation E BTransform only the valid side
andThen(A -> Validation E B) -> Validation E A -> Validation E BChain dependent validation steps
zipValidationValidation (NonEmptyList E) A -> Validation (NonEmptyList E) B -> Validation (NonEmptyList E) (A, B)Accumulate independent failures from both sides
fold(E -> B) -> (A -> B) -> Validation E A -> BCollapse a validation to one output type

Start with this table when you want the export inventory first; the detailed sections below explain the individual helpers in more depth.


Errors

A type alias for a non-empty list of errors. Used as the error carrier in zipValidation.

aivi
type Errors E = NonEmptyList E

Errors E guarantees at least one error is present when a validation fails. Import NonEmptyList from aivi.nonEmpty to construct values of this type.


isValid

Returns True if the validation holds a value.

Type: opt:(Validation E A) -> Bool

aivi
use aivi.validation (isValid)

type Validation Text Int -> Bool
func passed = v =>
    isValid v

isInvalid

Returns True if the validation has failed.

Type: opt:(Validation E A) -> Bool

aivi
use aivi.validation (isInvalid)

type Validation Text Int -> Bool
func rejected = v =>
    isInvalid v

getOrElse

Extracts the value from Valid, or returns the fallback if Invalid.

Type: fallback:A -> opt:(Validation E A) -> A

aivi
use aivi.validation (getOrElse)

type Validation Text Int -> Int
func safeValue = v =>
    getOrElse 0 v

mapErr

Transforms the error inside Invalid, leaving Valid untouched.

Type: transform:(E1 -> E2) -> opt:(Validation E1 A) -> Validation E2 A

aivi
use aivi.validation (mapErr)

type Text -> Int
func toCode = message =>
    42

type Validation Text Int -> (Validation Int Int)
func withErrorCode = v => v
  |> mapErr toCode

toResult

Converts a Validation to a Result. Valid value becomes Ok value; Invalid err becomes Err err.

Type: opt:(Validation E A) -> Result E A

aivi
use aivi.validation (toResult)

type Validation Text Int -> (Result Text Int)
func asResult = v =>
    toResult v

fromResult

Converts a Result to a Validation. Ok value becomes Valid value; Err error becomes Invalid error.

Type: opt:(Result E A) -> Validation E A

aivi
use aivi.validation (fromResult)

type Result Text Int -> (Validation Text Int)
func asValidation = r =>
    fromResult r

toOption

Converts a Validation to an Option, discarding any errors. Valid value becomes Some value; Invalid becomes None.

Type: opt:(Validation E A) -> Option A

aivi
use aivi.validation (toOption)

type Validation Text Int -> (Option Int)
func justValue = v =>
    toOption v

map

Transforms the value inside Valid using a function, leaving Invalid untouched.

Type: transform:(A -> B) -> opt:(Validation E A) -> Validation E B

aivi
use aivi.validation (map)

type Int -> Int
func double = n =>
    n * 2

type Validation Text Int -> (Validation Text Int)
func doubleValid = v => v
  |> map double

andThen

Chains a Validation-returning function over a Valid value. If the input is Invalid, the error is propagated without calling the function.

Unlike zipValidation, andThen stops at the first failure and does not accumulate errors.

Type: next:(A -> Validation E B) -> opt:(Validation E A) -> Validation E B

aivi
use aivi.validation (andThen)

type Int -> (Validation Text Int)
func ensurePositive = n => n > 0
 T|> Valid n
 F|> Invalid "must be positive"

type Validation Text Int -> (Validation Text Int)
func validateCount = v => v
  |> andThen ensurePositive

zipValidation

Combines two validations into one that holds a tuple of both values. If either or both sides are Invalid, all errors are accumulated into a NonEmptyList. This is the primary tool for parallel form validation.

Type: left:(Validation (NonEmptyList E) A) -> right:(Validation (NonEmptyList E) B) -> Validation (NonEmptyList E) (A, B)

aivi
use aivi.validation (zipValidation)

use aivi.nonEmpty (
    NonEmptyList
    singleton
)

type FieldError =
  | EmptyName
  | InvalidAge

type Text -> (Validation (NonEmptyList FieldError) Text)
func validateName = name => name
 ||> "" -> Invalid (singleton EmptyName)
 ||> _  -> Valid name

type Int -> (Validation (NonEmptyList FieldError) Int)
func validateAge = age => age > 0
 T|> Valid age
 F|> Invalid (singleton InvalidAge)

type Text -> Int -> (Validation (NonEmptyList FieldError) (Text, Int))
func validateForm = name age =>
    zipValidation (validateName name) (validateAge age)

If both validateName and validateAge fail, the result is Invalid containing both EmptyName and InvalidAge in a single list.


fold

Collapses a Validation to a single value by applying onValid to Valid or onInvalid to Invalid.

Type: onInvalid:(E -> B) -> onValid:(A -> B) -> opt:(Validation E A) -> B

aivi
use aivi.validation (fold)

use aivi.nonEmpty (
    NonEmptyList
    length
)

type NonEmptyList Text -> Int
func countErrors = errors =>
    length errors

type Int -> Int
func identity = n =>
    n

type Validation (NonEmptyList Text) Int -> Int
func scoreOrErrorCount = v =>
    fold countErrors identity v

(c) 2026 by Andreas Herd