Last active
December 30, 2018 08:16
-
-
Save bohdanszymanik/9cb630505ece9776fe88f8a963a7299a to your computer and use it in GitHub Desktop.
Getting my head round applicative validation
This file contains hidden or 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
// 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