Skip to content

Instantly share code, notes, and snippets.

@bohdanszymanik
Last active December 30, 2018 08:16
Show Gist options
  • Save bohdanszymanik/9cb630505ece9776fe88f8a963a7299a to your computer and use it in GitHub Desktop.
Save bohdanszymanik/9cb630505ece9776fe88f8a963a7299a to your computer and use it in GitHub Desktop.
Getting my head round applicative validation
// Exploring validation approaches
// (To start off a no validation option then...)
// Firstly by using an Option type to return Some error text or None if ok - well, actually we end up wrapping the output with Result just because we can
// Secondly by using a Result type and applicative validation a la https://fsharpforfunandprofit.com/posts/elevated-world-3/#validation
open System
module Result =
let apply fResult xResult =
match fResult, xResult with
| Result.Ok(f), Result.Ok(x) -> Result.Ok(f x)
| Result.Error(e), Result.Ok(x) -> Result.Error(e)
| Result.Ok(f), Result.Error(e) -> Result.Error(e)
| Result.Error(e1), Result.Error(e2) -> Result.Error( List.concat[e1;e2] )
let (<*>) = Result.apply
let (<!>) = Result.map
// a type to hold some subtypes for which various validations apply
type Colour = Red | Green | Blue | Other of string
type Shape = Square | Triangle | Point | Other of string
type Thing = {
Colour : Colour
Shape : Shape
}
module Thing =
// let's start with a function to create with no validations
let createUnsafe colour shape =
{Colour = colour; Shape=shape}
let validateShape shape =
match shape, System.DateTime.Today.Day with
| Triangle, _ -> None // triangles are always ok
| Square, d when d % 2 = 0 -> None
| Point, d when d % 2 = 1 -> None
| Square, d when d % 2 = 1 -> Some "Unlucky, must be an odd numbered day - no good for a square"
| Point, d when d % 2 = 0 -> Some "Unlucky, must be an even numbered day - no good for a point"
| _, _ -> Some "Not a Triangle, not a square on an even numbered day, or a Point on an odd numbered day"
let validateColour colour =
match colour with
| Green -> None
| Red -> None
| _ -> Some "Must be green or red otherwise not interested"
// here's one way to apply a collection of validations to the parameters that go into making a Thing and return validation results
let createSafeByApplyingValidationsToParametersFirst colour shape =
let validations = [(validateShape shape); (validateColour colour)]
if (validations |> List.filter Option.isSome |> List.isEmpty) then
Result.Ok({Shape = shape; Colour = colour})
else
Result.Error(validations)
// here's another way using the applicative approach
// we first define validation functions for each part of the Thing type returning Result types
// then we use Result.map to map the validations and Result.apply the results returning a combined result
let createShapeReturnAResult shape =
match shape, System.DateTime.Today.Day with
| Triangle, _ -> Result.Ok(Triangle) // triangles are always ok
| Square, d when d % 2 = 0 -> Result.Ok(Square)
| Point, d when d % 2 = 1 -> Result.Ok(Point)
| Square, d when d % 2 = 1 -> Result.Error ["Unlucky, must be an odd numbered day - no good for a square"]
| Point, d when d % 2 = 0 -> Result.Error ["Unlucky, must be an even numbered day - no good for a point"]
| _, _ -> Result.Error ["Not a Triangle, not a square on an even numbered day, or a Point on an odd numbered day"]
let createColourReturnAResult colour =
match colour with
| Green -> Result.Ok(Green)
| Red -> Result.Ok(Red)
| _ -> Result.Error ["Must be green or red otherwise not interested"]
let createSafeWithApplicativeResult colour shape =
// read it like this
// an instance of a Thing mapped against the two validation functions
createUnsafe <!> (createColourReturnAResult colour) <*> (createShapeReturnAResult shape)
let createSafeWithApplicativeResult2 colour shape =
// this time without the infix
(createShapeReturnAResult shape)
|> Result.apply ( (createColourReturnAResult colour)
|> Result.map createUnsafe )
Thing.createSafeByApplyingValidationsToParametersFirst Red Square // the list of validations is returned so you get a mix of Some and None - but returning None isn't really useful
Thing.createSafeByApplyingValidationsToParametersFirst Blue Square
Thing.createSafeByApplyingValidationsToParametersFirst Red Triangle
Thing.createSafeByApplyingValidationsToParametersFirst Red Point
Thing.createSafeWithApplicativeResult Red Square
Thing.createSafeWithApplicativeResult Blue Square
Thing.createSafeWithApplicativeResult Red Triangle
Thing.createSafeWithApplicativeResult Red Point
Thing.createSafeWithApplicativeResult ( Colour.Other("Orange") ) Point
Thing.createSafeWithApplicativeResult2 ( Colour.Other("Orange") ) Point
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment