question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

WIP: Validation CE Builder

See original GitHub issue

Description

Not directly related to https://github.com/fsprojects/FSharpPlus/issues/388 But more about the newly released .NET + F# 5.0. The idea would be to provide a dedicated CE builder for validation types supporting the and! keyword.

Sources:

// First, define a 'zip' function
module Result =
    let zip x1 x2 = 
        match x1,x2 with
        | Ok x1res, Ok x2res -> Ok (x1res, x2res)
        | Error e, _ -> Error e
        | _, Error e -> Error e

// Next, define a builder with 'MergeSources' and 'BindReturn'
type ResultBuilder() = 
    member _.MergeSources(t1: Result<'T,'U>, t2: Result<'T1,'U>) = Result.zip t1 t2
    member _.BindReturn(x: Result<'T,'U>, f) = Result.map f x

let result = ResultBuilder()

let run r1 r2 r3 =        
    // And here is our applicative!
    let res1: Result<int, string> =
        result { 
            let! a = r1 
            and! b = r2
            and! c = r3
            return a + b - c 
        }

    match res1 with
    | Ok x -> printfn "%s is: %d" (nameof res1) x
    | Error e -> printfn "%s is: %s" (nameof res1) e

let printApplicatives () =
    let r1 = Ok 2
    let r2 = Ok 3 // Error "fail!"
    let r3 = Ok 4

    run r1 r2 r3
    run r1 (Error "failure!") r3

Repro steps

Please provide the steps required to reproduce the problem

let runValidations v1 v2 v3 =
    let val1 =
        validation {
            let! a = v1
            and! b = v2
            and! c = v3
            return a + b - c
        }

    match val1 with
    | Success s -> printfn "%A is: %A" (nameof val1) s
    | Failure f -> printfn "%A is: %A" (nameof val1) f


let printValidations () =
    let r1 = Validation<string list, int32>.Success 2
    let r2 = Validation<string list, int32>.Success 3
    let r3 = Validation<string list, int32>.Success 4

    runValidations r1 r2 r3
    runValidations
        r1
        (Validation<string list, int32>.Failure [ "Meow 1" ])
        (Validation<string list, int32>.Failure [ "Meow 2" ])

[<EntryPoint>]
let main _ =
    printValidations()
    0
  1. Step B

Expected behavior

validation CE builder should be defined and the output of the program should be:

"val1" is: 1
"val1" is: ["Meow 1"; "Meow 2"]

Actual behavior

The value or constructor 'validation' is not defined.

Known workarounds

open FSharpPlus.Data
module Result =
    let zip r1 r2 =
        match r1, r2 with
        | Ok o1, Ok o2 -> Ok (o1, o2)
        | Error e1, _ -> Error e1
        | _, Error e2 -> Error e2
​
// Next, define a builder with 'MergeSources' and 'BindReturn'
type ResultBuilder() =
    member _.MergeSources(t1: Result<'T,'U>, t2: Result<'T1,'U>) = Result.zip t1 t2
    member _.BindReturn(x: Result<'T,'U>, f) = Result.map f x
​
module Validation =
    let zip x1 x2 =
        match (x1, x2) with
        | Success s1, Success s2 -> Success (s1, s2)
        | Failure f1, Success _  -> Failure f1
        | Success _,  Failure f2 -> Failure f2
        | Failure f1, Failure f2 -> Failure (f1 @ f2)
​
type ValidationBuilder() =
    member _.MergeSources(t1, t2) = Validation.zip t1 t2
    member _.BindReturn(x, f) = Validation.map f x
    member _.Bind(x, f) = Validation.bind f x
​
​
let result = ResultBuilder()
let validation = ValidationBuilder()
​
let runResults r1 r2 r3 =
    // And here is our applicative!
    let res1: Result<int, string> =
        result {
            let! a = r1
            and! b = r2
            and! c = r3
            return a + b - c
        }
​
    match res1 with
    | Ok o -> printfn "%A is: %A" (nameof res1) o
    | Error e -> printfn "%A is: %A" (nameof res1) e
​
let runValidations v1 v2 v3 =
    let val1 =
        validation {
            let! a = v1
            and! b = v2
            and! c = v3
            return a + b - c
        }
​
    match val1 with
    | Success s -> printfn "%A is: %A" (nameof val1) s
    | Failure f -> printfn "%A is: %A" (nameof val1) f
​
let printResults () =
    let r1 = Ok 2
    let r2 = Ok 3
    let r3 = Ok 4
​
    runResults r1 r2 r3
    runResults r1 (Error "failure!") r3
​
let printValidations () =
    let r1 = Validation<string list, int32>.Success 2
    let r2 = Validation<string list, int32>.Success 3
    let r3 = Validation<string list, int32>.Success 4
​
    runValidations r1 r2 r3
    runValidations
        r1
        (Validation<string list, int32>.Failure [ "Meow 1" ])
        (Validation<string list, int32>.Failure [ "Meow 2" ])
​
[<EntryPoint>]
let main _ =
    printResults()
    printValidations()
    0

Output:

"res1" is: 1
"res1" is: "failure!"
"val1" is: 1
"val1" is: ["Meow 1"; "Meow 2"]

Related information

  • Operating system: Windows 10 professional
  • Branch: master
  • .NET Runtime, CoreCLR or Mono Version: .NET 5.0 + F# 5.0 (lang preview enabled)
  • Performance information, links to performance testing scripts

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Comments:11 (7 by maintainers)

github_iconTop GitHub Comments

3reactions
gustycommented, Jul 8, 2021

Yes, that’s the plan, either incorporate it in monad or create a separate applicative CE.

We started with the former but we found an F# bug which seems it will be solved soon.

Therefore we felt it was safer to wait until that bug is solved so we can evaluate properly which solution is the best, in the meantime adding the BindReturn method by hand should be the way to go.

2reactions
natalie-o-perretcommented, Jul 8, 2021

No, it doesn’t. You are getting that error because the runValidations is not inline, and it has to have the inline modifier because it has a static requirement for the + operator, which means it’s generic over semigroups. BTW: As discussed by slack, now with #425 you can do this:

#r "nuget: FSharpPlus,1.2.0-CI01958"

open FSharpPlus
open FSharpPlus.Data

let resultCE<'t,'e> = MonadFxStrictBuilder<Result<'t,'e>> ()

type AppicativeValidationBuilder<'e,'t> () =
    inherit MonadFxStrictBuilder<Validation<'e,'t>> ()
    member _.BindReturn (x, f: 'T -> 't) = Validation.map f x : Validation<'e,'t>

let validation<'e,'t> = AppicativeValidationBuilder<'e,'t> ()

in your sample code, it should work fine. Don’t use result for the builder because it will pick the result function instead. Let me know if you feel we can close this issue.

Is it still required to inherit from MonadFxStrictBuilder and add _.BindReturn in order to use applicative CE’s with F#+ or has this been incorporated somewhere in the latest release?

If not would it be possible to add an applicative (or applicative') builder which extends the monad (or monad') builder with BindReturn?

Fyi, this is what we have been using at my company for the past couple of months or so:

open FSharpPlus
open FSharpPlus.Data


let result'<'Ok,'Error> = MonadFxStrictBuilder<Result<'Ok,'Error>> ()
let result<'Ok,'Error> = MonadFxBuilder<Result<'Ok, 'Error>>()

let option'<'T> = MonadFxStrictBuilder<Option<'T>> ()
let option<'T> = MonadFxBuilder<Option<'T>> ()

let state'<'S, 'T> = MonadFxStrictBuilder<State<'S, 'T>> ()
let state<'S, 'T> = MonadFxBuilder<State<'S, 'T>> ()

type ApplicativeValidationStrictBuilder<'Failure,'Success> () =
    inherit MonadFxStrictBuilder<Validation<'Failure,'Success>> ()
    member _.BindReturn (x, f: 'T -> 'Success) = Validation.map f x : Validation<'Failure,'Success>

type ApplicativeValidationBuilder<'Failure,'Success> () =
    inherit MonadFxBuilder<Validation<'Failure,'Success>> ()
    member _.BindReturn (x, f: 'T -> 'Success) = Validation.map f x : Validation<'Failure,'Success>

let validation'<'Failure,'Success> = ApplicativeValidationStrictBuilder<'Failure,'Success> ()
let validation<'Failure,'Success> = ApplicativeValidationBuilder<'Failure,'Success> ()
Read more comments on GitHub >

github_iconTop Results From Across the Web

The WIP:Validate Push Components At WIP Completion ...
Oracle Work in Process - Version 12.2.8 and later: The WIP:Validate Push Components At WIP Completion Profile Option Is Not Working ...
Read more >
ProjectWise: Project Delivery
Bentley Infrastructure Cloud. Powered by iTwin. Manage and visualize project, construction, or asset information in 2D, 3D, ...
Read more >
Problem with Applicative CEs and 'Zero' · Issue #10379
1 reaction. Sorry, something went wrong. @gusty gusty mentioned this issue on Jul 8, 2021. WIP: Validation CE Builder fsprojects/FSharpPlus#397.
Read more >
WIP Validation in Blazor Kanban Component
WIP Validation in Blazor Kanban Component. 15 Dec 202210 minutes to read. Validate particular column using the MinCount or MaxCount properties.
Read more >
Configure WIP Lane Limit
You can set a limit to the number of cards added to a lane. If the limit is not set, any number of...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found