Created
June 14, 2022 08:29
-
-
Save Savelenko/5d5f66a224969d317bc75726899c5a8e to your computer and use it in GitHub Desktop.
Applicative validation with abstract static interface members
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(* | |
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