Skip to content

Instantly share code, notes, and snippets.

@Savelenko
Created June 14, 2022 08:29
Show Gist options
  • Save Savelenko/5d5f66a224969d317bc75726899c5a8e to your computer and use it in GitHub Desktop.
Save Savelenko/5d5f66a224969d317bc75726899c5a8e to your computer and use it in GitHub Desktop.
Applicative validation with abstract static interface members
(*
The original applicative validation type is defined like this in FsToolkit.ErrorHandling:
*)
/// Validation<'a, 'err> is defined as Result<'a, 'err list> meaning you can use many of the functions found in the
/// Result module.
type Validation<'ok, 'error> = Result<'ok, List<'error>>
(*
We can abstract the `List` away by requiring any structure which supports concatenation, of which the list's (@) is an
instance of. Structures supporting this are called semigroups:
*)
type Semigroup<'t> =
(*static*) abstract Concat : 't * 't -> 't // static abstract support in F# is pending
(*
Now redefine the generalized validation type. Note that `List` is gone:
*)
type Validation2<'ok, 'error when 'error :> Semigroup<'error>> = Result<'ok, 'error>
(*
Next we exploit the semigroup in the following central function on `Validation2`:
*)
let inline apply
(applier: Validation2<'okInput -> 'okOutput, 'error>)
(input: Validation2<'okInput, 'error>)
: Validation2<'okOutput, 'error> =
match applier, input with
| Ok f, Ok x -> Ok(f x)
| Error errs, Ok _
| Ok _, Error errs -> Error errs
// The following syntax is not yet supported in F#
| Error errs1, Error errs2 -> Error('error.Concat (errs1, errs2)) // originally errs1 @ errs2
(*
The `List`-based validation behavior can be recovered by having `List` implement our `Semigroup` interface.
Unfortunately this is only possible with type classes / traits and not with regular interfaces, where implementations
can be provided separately from type definitions themselves. In order to circumvent this, we define a wrapper for lists:
*)
type Multiple<'error> = Multiple of List<'error> with
interface Semigroup<Multiple<'error>> with
member this.Concat(Multiple left, Multiple right): Multiple<'error> = // `this` will not be needed later
Multiple (left @ right) // @ is Concat for lists
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment