WIP: Validation CE Builder
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:
- https://devblogs.microsoft.com/dotnet/announcing-f-5/#applicative-computation-expressions
- https://gist.githubusercontent.com/cartermp/8540e3fb1edf647f3c7ee8e4352c5941/raw/0c6893a68499499eca517118d3db80c379fc8799/fs5-applicatives.fsx
// 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
- 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
Actual behavior The value or constructor 'validation' is not defined.
Have you tried using monad instead of validation ?
Actual behavior The value or constructor 'validation' is not defined.
Have you tried using
monadinstead ofvalidation?
Just did, so turns out this one does relate to: https://github.com/fsprojects/FSharpPlus/issues/388
Closing that one and creating the lists of the dedicated CEs in the other issue.
I think if you use monad, intellisense will be happy, but you'll get the unfortunate unknown(1,1): error FS0073: internal error: Undefined or unsolved type variable: ^_?1831670 error.
Using monad' (since Validation is strict) seems to solve the issue.
Well I got a 164685 when using the non-strict monad CE builder:
internal error: Undefined or unsolved type variable: ^_?164685
but yea I guess the number doesn't really matter here, innit?
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.
No, it doesn't. You are getting that error because the
runValidationsis not inline, and it has to have theinlinemodifier 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
resultfor the builder because it will pick theresultfunction instead.Let me know if you feel we can close this issue.
Like I said ydy, I have been running some tests tdy and so far, it looks promising! Will finish testing this coming weekend.
No, it doesn't. You are getting that error because the
runValidationsis not inline, and it has to have theinlinemodifier 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
resultfor the builder because it will pick theresultfunction 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?
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.
No, it doesn't. You are getting that error because the
runValidationsis not inline, and it has to have theinlinemodifier 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
resultfor the builder because it will pick theresultfunction instead. Let me know if you feel we can close this issue.Is it still required to inherit from
MonadFxStrictBuilderand add_.BindReturnin 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(orapplicative') builder which extends themonad(ormonad') builder withBindReturn?
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> ()
FWIW I've just got the following fairly generic version working
type ApplicativeBuilder<'a>() =
inherit MonadFxStrictBuilder<'a>()
member inline _.BindReturn(x, f) = map f x
let applicative = ApplicativeBuilder()
and using it like this
applicative {
let! firstValue = getFirstValue()
and! secondValue = getSecondValue()
return
{ FirstValue = firstValue
SecondValue = secondValue }
}
And in my case getFirstValue() etc were returning Validation<NonEmptyList<string>, _>
Cool, we should add a snippets section in the docs.