diff --git a/.github/workflows/fable.yml b/.github/workflows/fable.yml index e2f4f4549..47d9ee8f8 100644 --- a/.github/workflows/fable.yml +++ b/.github/workflows/fable.yml @@ -43,7 +43,14 @@ jobs: run: git submodule update --init --recursive - name: Remove global json run: rm global.json - - name: Setup .NET Core + - name: Remove global json in subfolder + run: rm global.json + working-directory: tests/FSharpPlusFable.Tests + - name: Setup .NET Core 7 + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 7.0.100 + - name: Setup .NET Core 6 uses: actions/setup-dotnet@v1 with: dotnet-version: 6.0.201 diff --git a/FSharpPlus.sln b/FSharpPlus.sln index 2f5060760..63afb4df6 100644 --- a/FSharpPlus.sln +++ b/FSharpPlus.sln @@ -98,6 +98,8 @@ Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharpPlusFable.Tests", "te EndProject Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Benchmarks", "tests\benchmarks\Benchmarks.fsproj", "{EEFF08EB-8B0C-4F63-9425-4281EFF12087}" EndProject +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "docsTool", "docsrc\tools\docsTool.fsproj", "{ACBBD11E-0746-4B9D-9CED-A90FE5824CE2}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -169,6 +171,14 @@ Global {EEFF08EB-8B0C-4F63-9425-4281EFF12087}.Fable|Any CPU.Build.0 = Debug|Any CPU {EEFF08EB-8B0C-4F63-9425-4281EFF12087}.Fable3|Any CPU.ActiveCfg = Debug|Any CPU {EEFF08EB-8B0C-4F63-9425-4281EFF12087}.Fable3|Any CPU.Build.0 = Debug|Any CPU + {ACBBD11E-0746-4B9D-9CED-A90FE5824CE2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ACBBD11E-0746-4B9D-9CED-A90FE5824CE2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ACBBD11E-0746-4B9D-9CED-A90FE5824CE2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ACBBD11E-0746-4B9D-9CED-A90FE5824CE2}.Release|Any CPU.Build.0 = Release|Any CPU + {ACBBD11E-0746-4B9D-9CED-A90FE5824CE2}.Fable|Any CPU.ActiveCfg = Debug|Any CPU + {ACBBD11E-0746-4B9D-9CED-A90FE5824CE2}.Fable|Any CPU.Build.0 = Debug|Any CPU + {ACBBD11E-0746-4B9D-9CED-A90FE5824CE2}.Fable3|Any CPU.ActiveCfg = Debug|Any CPU + {ACBBD11E-0746-4B9D-9CED-A90FE5824CE2}.Fable3|Any CPU.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -182,6 +192,7 @@ Global {7A5B766E-8141-4D8A-B3EB-91422FDBDF71} = {ED8079DD-2B06-4030-9F0F-DC548F98E1C4} {1CCD1BFB-60E4-40AA-B534-3C5EEE5E1E83} = {ED8079DD-2B06-4030-9F0F-DC548F98E1C4} {EEFF08EB-8B0C-4F63-9425-4281EFF12087} = {ED8079DD-2B06-4030-9F0F-DC548F98E1C4} + {ACBBD11E-0746-4B9D-9CED-A90FE5824CE2} = {83F16175-43B1-4C90-A1EE-8E351C33435D} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {789B5FFA-7891-4F60-831E-42C3C5ED2C51} diff --git a/docsrc/content/abstraction-applicative.fsx b/docsrc/content/abstraction-applicative.fsx index df9076d8f..6ffca1c40 100644 --- a/docsrc/content/abstraction-applicative.fsx +++ b/docsrc/content/abstraction-applicative.fsx @@ -67,6 +67,7 @@ From F# - ``KeyValuePair<'Key,'T>`` - ``'Monoid * 'T`` - ``Task<'T>`` + - ``ValueTask<'T>`` - ``'R->'T`` - ``Expr<'T>`` - ``ResizeArray<'T>`` diff --git a/docsrc/content/abstraction-comonad.fsx b/docsrc/content/abstraction-comonad.fsx index fe17ba083..1b6272bf2 100644 --- a/docsrc/content/abstraction-comonad.fsx +++ b/docsrc/content/abstraction-comonad.fsx @@ -67,6 +67,7 @@ From .Net/F# - ``Id<'T>`` - ``('W * 'T)`` - ``'Monoid -> 'T`` + - ``ValueTask<'T>`` From F#+ diff --git a/docsrc/content/abstraction-functor.fsx b/docsrc/content/abstraction-functor.fsx index f481be404..96b0f8525 100644 --- a/docsrc/content/abstraction-functor.fsx +++ b/docsrc/content/abstraction-functor.fsx @@ -58,6 +58,7 @@ From F# - ``Map<'Key,'T>`` - ``'Monoid * 'T`` - ``Task<'T>`` + - ``ValueTask<'T>`` - ``'R->'T`` - ``Expr<'T>`` - ``Dictionary<'Key,'T>`` diff --git a/docsrc/content/abstraction-monad.fsx b/docsrc/content/abstraction-monad.fsx index 20f8b06ef..83c1384c1 100644 --- a/docsrc/content/abstraction-monad.fsx +++ b/docsrc/content/abstraction-monad.fsx @@ -75,6 +75,7 @@ From F# - ``Choice<'T,'U>`` - ``'Monoid * 'T`` - ``Task<'T>`` + - ``ValueTask<'T>`` - ``'R->'T`` - ``ResizeArray<'T>`` diff --git a/docsrc/content/abstraction-monoid.fsx b/docsrc/content/abstraction-monoid.fsx index 6d10b0177..25c467606 100644 --- a/docsrc/content/abstraction-monoid.fsx +++ b/docsrc/content/abstraction-monoid.fsx @@ -60,6 +60,7 @@ From .Net/F# - ``Tuple<'Monoid1* ... *'MonoidN>`` - ``'Monoid1* ... *'MonoidN`` - ``Task<'T>`` + - ``ValueTask<'T>`` - ``'T->'Monoid`` - ``Async<'T>`` - ``Expr<'T>`` diff --git a/docsrc/content/abstraction-semigroup.fsx b/docsrc/content/abstraction-semigroup.fsx index 5926b8cf0..a5d0c9187 100644 --- a/docsrc/content/abstraction-semigroup.fsx +++ b/docsrc/content/abstraction-semigroup.fsx @@ -44,6 +44,7 @@ From .Net/F# - ``Tuple<*>`` - ``'T1* ... *'Tn`` - ``Task<'T>`` + - ``ValueTask<'T>`` - ``'T->'Semigroup`` - ``Async<'T>`` - ``Expr<'T>`` diff --git a/docsrc/content/extensions.fsx b/docsrc/content/extensions.fsx index 84abdc81f..bb38da66a 100644 --- a/docsrc/content/extensions.fsx +++ b/docsrc/content/extensions.fsx @@ -259,14 +259,20 @@ Collections / Traversable types: * zip, unzip, * unionWith, union, intersectWith, intersect -Async and Tasks: -================ +Async, Task and ValueTask: +========================== * [ Task ](reference/fsharpplus-task.html) * map, map2, map3 * apply * zip * join * ignore + * [ ValueTask ](reference/fsharpplus-valueTask.html) + * map, map2, map3 + * apply + * zip + * join + * ignore * [ Async ](reference/fsharpplus-async.html) * map, map2 * zip diff --git a/src/FSharpPlus/Control/Applicative.fs b/src/FSharpPlus/Control/Applicative.fs index 25dbad43f..e4c3c5ffc 100644 --- a/src/FSharpPlus/Control/Applicative.fs +++ b/src/FSharpPlus/Control/Applicative.fs @@ -30,6 +30,9 @@ type Apply = #if !FABLE_COMPILER static member ``<*>`` (f: Task<_> , x: Task<'T> , []_output: Task<'U> , []_mthd: Apply) = Task.apply f x : Task<'U> #endif + #if NETSTANDARD2_1 && !FABLE_COMPILER + static member ``<*>`` (f: ValueTask<_> , x: ValueTask<'T> , []_output: ValueTask<'U> , []_mthd: Apply) = ValueTask.apply f x : ValueTask<'U> + #endif static member ``<*>`` (f: Async<_> , x: Async<'T> , []_output: Async<'U> , []_mthd: Apply) = Async.apply f x : Async<'U> static member ``<*>`` (f: option<_> , x: option<'T> , []_output: option<'U> , []_mthd: Apply) = Option.apply f x : option<'U> static member ``<*>`` (f: voption<_> , x: voption<'T> , []_output: voption<'U> , []_mthd: Apply) = ValueOption.apply f x : voption<'U> @@ -82,6 +85,9 @@ type Lift2 = #if !FABLE_COMPILER static member Lift2 (f, (x: Task<'T> , y: Task<'U> ), _mthd: Lift2) = Task.map2 f x y #endif + #if NETSTANDARD2_1 && !FABLE_COMPILER + static member Lift2 (f, (x: ValueTask<'T> , y: ValueTask<'U> ), _mthd: Lift2) = ValueTask.map2 f x y + #endif static member Lift2 (f, (x , y ), _mthd: Lift2) = Async.map2 f x y static member Lift2 (f, (x , y ), _mthd: Lift2) = Option.map2 f x y @@ -124,6 +130,9 @@ type Lift3 = #if !FABLE_COMPILER static member Lift3 (f, (x: Task<'T> , y: Task<'U> , z: Task<'V> ), _mthd: Lift3) = Task.map3 f x y z #endif + #if NETSTANDARD2_1 && !FABLE_COMPILER + static member Lift3 (f, (x: ValueTask<'T> , y: ValueTask<'U> , z: ValueTask<'V> ), _mthd: Lift3) = ValueTask.map3 f x y z + #endif static member Lift3 (f, (x , y , z ), _mthd: Lift3) = Async.map3 f x y z static member Lift3 (f, (x , y , z ), _mthd: Lift3) = Option.map3 f x y z diff --git a/src/FSharpPlus/Control/Comonad.fs b/src/FSharpPlus/Control/Comonad.fs index 1736a9be7..c2a7b4403 100644 --- a/src/FSharpPlus/Control/Comonad.fs +++ b/src/FSharpPlus/Control/Comonad.fs @@ -28,6 +28,9 @@ type Extract = #if !FABLE_COMPILER static member Extract (f: Task<'T> ) = f.Result #endif + #if NETSTANDARD2_1 && !FABLE_COMPILER + static member Extract (f: ValueTask<'T> ) = f.Result + #endif static member inline Invoke (x: '``Comonad<'T>``) : 'T = let inline call_2 (_mthd: ^M, x: ^I) = ((^M or ^I) : (static member Extract : _ -> _) x) call_2 (Unchecked.defaultof, x) @@ -57,6 +60,14 @@ type Extend = elif k.Status = TaskStatus.Canceled then tcs.SetCanceled () elif k.Status = TaskStatus.Faulted then tcs.SetException k.Exception.InnerExceptions) |> ignore tcs.Task + + + #endif + #if NETSTANDARD2_1 && !FABLE_COMPILER + static member (=>>) (g: ValueTask<'T> , f: ValueTask<'T> -> 'U ) : ValueTask<'U> = + backgroundTask { + return! f g + } |> ValueTask<'U> #endif // Restricted Comonads diff --git a/src/FSharpPlus/Control/Functor.fs b/src/FSharpPlus/Control/Functor.fs index 69242cbe6..59c967fcd 100644 --- a/src/FSharpPlus/Control/Functor.fs +++ b/src/FSharpPlus/Control/Functor.fs @@ -67,6 +67,9 @@ type Map = #if !FABLE_COMPILER static member Map ((x: Task<'T> , f: 'T->'U), _mthd: Map) = Task.map f x : Task<'U> #endif + #if NETSTANDARD2_1 && !FABLE_COMPILER + static member Map ((x: ValueTask<'T> , f: 'T->'U), _mthd: Map) = ValueTask.map f x : ValueTask<'U> + #endif static member Map ((x: option<_> , f: 'T->'U), _mthd: Map) = Option.map f x #if !FABLE_COMPILER static member Map ((x: voption<_> , f: 'T->'U), _mthd: Map) = ValueOption.map f x @@ -148,6 +151,9 @@ type Unzip = #if !FABLE_COMPILER static member Unzip ((source: Task<'T * 'U> , _output: Task<'T> * Task<'U> ) , _mthd: Unzip ) = Map.Invoke fst source, Map.Invoke snd source #endif + #if NETSTANDARD2_1 && !FABLE_COMPILER + static member Unzip ((source: ValueTask<'T * 'U> , _output: ValueTask<'T> * ValueTask<'U> ) , _mthd: Unzip ) = Map.Invoke fst source, Map.Invoke snd source + #endif static member Unzip ((source: option<'T * 'U> , _output: option<'T> * option<'U> ) , _mthd: Unzip ) = Option.unzip source static member Unzip ((source: voption<'T * 'U> , _output: voption<'T> * voption<'U> ) , _mthd: Unzip ) = ValueOption.unzip source @@ -214,6 +220,9 @@ type Zip = #if !FABLE_COMPILER static member Zip ((x: Task<'T> , y: Task<'U> , _output: Task<'T*'U> ), _mthd: Zip) = Task.zip x y #endif + #if NETSTANDARD2_1 && !FABLE_COMPILER + static member Zip ((x: ValueTask<'T> , y: ValueTask<'U> , _output: ValueTask<'T*'U> ), _mthd: Zip) = ValueTask.zip x y + #endif static member inline Invoke (source1: '``ZipFunctor<'T1>``) (source2: '``ZipFunctor<'T2>``) = let inline call_4 (a: ^a, b: ^b, c: ^c, d: ^d) = ((^a or ^b or ^c or ^d) : (static member Zip : (_*_*_)*_ -> _) (b, c, d), a) diff --git a/src/FSharpPlus/Control/Monad.fs b/src/FSharpPlus/Control/Monad.fs index 8557d2fd4..ad3bd699d 100644 --- a/src/FSharpPlus/Control/Monad.fs +++ b/src/FSharpPlus/Control/Monad.fs @@ -16,12 +16,16 @@ open FSharpPlus.Internals.Prelude // Monad class ------------------------------------------------------------ type Bind = - static member (>>=) (source: Lazy<'T> , f: 'T -> Lazy<'U> ) = lazy (f source.Value).Value : Lazy<'U> - static member (>>=) (source: seq<'T> , f: 'T -> seq<'U> ) = Seq.bind f source : seq<'U> + static member (>>=) (source: Lazy<'T> , f: 'T -> Lazy<'U> ) = lazy (f source.Value).Value : Lazy<'U> + static member (>>=) (source: seq<'T> , f: 'T -> seq<'U> ) = Seq.bind f source : seq<'U> #if !FABLE_COMPILER - static member (>>=) (source: Task<'T> , f: 'T -> Task<'U> ) = Task.bind f source : Task<'U> - static member (>>=) (source , f: 'T -> _ ) = Nullable.bind f source : Nullable<'U> + static member (>>=) (source: Task<'T> , f: 'T -> Task<'U> ) = Task.bind f source : Task<'U> + static member (>>=) (source , f: 'T -> _ ) = Nullable.bind f source : Nullable<'U> #endif + #if NETSTANDARD2_1 && !FABLE_COMPILER + static member (>>=) (source: ValueTask<'T> , f: 'T -> ValueTask<'U> ) = ValueTask.bind f source : ValueTask<'U> + #endif + static member (>>=) (source , f: 'T -> _ ) = Option.bind f source : option<'U> #if !FABLE_COMPILER static member (>>=) (source , f: 'T -> _ ) = ValueOption.bind f source : voption<'U> @@ -69,13 +73,16 @@ type Bind = type Join = inherit Default1 - static member inline Join (x: '``Monad<'Monad<'T>>``, []_output: '``Monad<'T>`` , []_mthd: Default2) = Bind.InvokeOnInstance x id : '``Monad<'T>`` - static member inline Join (x: '``Monad<'Monad<'T>>``, []_output: '``Monad<'T>`` , []_mthd: Default1) = ((^``Monad<'Monad<'T>>`` or ^``Monad<'T>``) : (static member Join : _ -> _) x) : '``Monad<'T>`` - static member Join (x: Lazy> , []_output: Lazy<'T> , []_mthd: Join ) = lazy x.Value.Value : Lazy<'T> - static member Join (x: seq> , []_output: seq<'T> , []_mthd: Join ) = Seq.concat x : seq<'T> - static member Join (x: Id<_> , []_output: Id<'T> , []_mthd: Join ) = x.getValue : Id<'T> - #if !FABLE_COMPILER - static member Join (x: Task> , []_output: Task<'T> , []_mthd: Join ) = Task.join x : Task<'T> + static member inline Join (x: '``Monad<'Monad<'T>>`` , []_output: '``Monad<'T>`` , []_mthd: Default2) = Bind.InvokeOnInstance x id : '``Monad<'T>`` + static member inline Join (x: '``Monad<'Monad<'T>>`` , []_output: '``Monad<'T>`` , []_mthd: Default1) = ((^``Monad<'Monad<'T>>`` or ^``Monad<'T>``) : (static member Join : _ -> _) x) : '``Monad<'T>`` + static member Join (x: Lazy> , []_output: Lazy<'T> , []_mthd: Join ) = lazy x.Value.Value : Lazy<'T> + static member Join (x: seq> , []_output: seq<'T> , []_mthd: Join ) = Seq.concat x : seq<'T> + static member Join (x: Id<_> , []_output: Id<'T> , []_mthd: Join ) = x.getValue : Id<'T> + #if !FABLE_COMPILER + static member Join (x: Task> , []_output: Task<'T> , []_mthd: Join ) = Task.join x : Task<'T> + #endif + #if NETSTANDARD2_1 && !FABLE_COMPILER + static member Join (x: ValueTask> , []_output: ValueTask<'T> , []_mthd: Join ) = ValueTask.join x : ValueTask<'T> #endif static member Join (x , []_output: option<'T> , []_mthd: Join ) = Option.flatten x : option<'T> #if !FABLE_COMPILER @@ -134,6 +141,9 @@ type Return = #if !FABLE_COMPILER static member Return (_: 'T Task , _: Return ) = fun x -> Task.FromResult x : 'T Task #endif + #if NETSTANDARD2_1 && !FABLE_COMPILER + static member Return (_: 'T ValueTask , _: Return ) = fun x -> ValueTask.FromResult x : 'T ValueTask + #endif static member Return (_: option<'a> , _: Return ) = fun x -> Some x : option<'a> static member Return (_ : voption<'a> , _: Return ) = fun x -> ValueSome x : voption<'a> static member Return (_: list<'a> , _: Return ) = fun x -> [ x ] : list<'a> @@ -180,6 +190,10 @@ type Delay = static member inline Invoke (source : unit -> '``Monad<'T>``) : '``Monad<'T>`` = Bind.Invoke (Return.Invoke ()) source #endif + + #if NETSTANDARD2_1 && !FABLE_COMPILER + static member Delay (_mthd: Delay , x: unit-> ValueTask<_> , _ ) = x () : ValueTask<'T> + #endif [] diff --git a/src/FSharpPlus/Control/Monoid.fs b/src/FSharpPlus/Control/Monoid.fs index 534ce353b..6ddedcf17 100644 --- a/src/FSharpPlus/Control/Monoid.fs +++ b/src/FSharpPlus/Control/Monoid.fs @@ -107,6 +107,14 @@ type Plus with static member inline ``+`` (x: 'a Task, y: 'a Task, []_mthd: Plus) = Task.map2 Plus.Invoke x y #endif +#if NETSTANDARD2_1 && !FABLE_COMPILER +type Plus with + + static member inline ``+`` (x: 'a ValueTask, y: 'a ValueTask, []_mthd: Plus) = ValueTask.map2 Plus.Invoke x y + +#endif + + static member inline ``+`` (x: Map<'a,'b> , y , []_mthd: Plus) = Map.unionWith Plus.Invoke x y static member inline ``+`` (x: Dictionary<'Key,'Value>, y: Dictionary<'Key,'Value>, []_mthd: Plus) = diff --git a/src/FSharpPlus/Control/Numeric.fs b/src/FSharpPlus/Control/Numeric.fs index d445a0a3c..d57e16fd6 100644 --- a/src/FSharpPlus/Control/Numeric.fs +++ b/src/FSharpPlus/Control/Numeric.fs @@ -183,6 +183,11 @@ type Zero with s.SetResult v s.Task #endif + #if NETSTANDARD2_1 && !FABLE_COMPILER + static member inline Zero (_: ValueTask<'a>, _: Zero) : ValueTask<'a> = + let (v: 'a) = Zero.Invoke () + ValueTask<'a>(v) + #endif static member inline Zero (_: 'T->'Monoid , _: Zero) = (fun _ -> Zero.Invoke ()) : 'T->'Monoid static member inline Zero (_: Async<'a> , _: Zero) = let (v: 'a) = Zero.Invoke () in async.Return v #if !FABLE_COMPILER diff --git a/src/FSharpPlus/Extensions/Task.fs b/src/FSharpPlus/Extensions/Task.fs index 41694accd..0f1e243ed 100644 --- a/src/FSharpPlus/Extensions/Task.fs +++ b/src/FSharpPlus/Extensions/Task.fs @@ -232,13 +232,13 @@ module Task = else let tcs = TaskCompletionSource () if task.Status = TaskStatus.Faulted then - tcs.SetException task.Exception.InnerExceptions |> ignore + tcs.SetException task.Exception.InnerExceptions elif task.Status = TaskStatus.Canceled then tcs.SetCanceled () else let k (t: Task) : unit = - if t.IsCanceled then tcs.SetCanceled () |> ignore - elif t.IsFaulted then tcs.SetException t.Exception |> ignore + if t.IsCanceled then tcs.SetCanceled () + elif t.IsFaulted then tcs.SetException t.Exception else tcs.SetResult () task.ContinueWith k |> ignore tcs.Task diff --git a/src/FSharpPlus/Extensions/ValueTask.fs b/src/FSharpPlus/Extensions/ValueTask.fs new file mode 100644 index 000000000..2e1eaac9e --- /dev/null +++ b/src/FSharpPlus/Extensions/ValueTask.fs @@ -0,0 +1,103 @@ +namespace FSharpPlus + +#if NETSTANDARD2_1 && !FABLE_COMPILER + +/// Additional operations on ValueTask<'T> +[] +module ValueTask = + + open System.Threading + open System.Threading.Tasks + + let FromResult<'T> (result : 'T) = + ValueTask<'T>(result) + + let FromException<'T> (e : exn) = + ValueTask<'T>(Task.FromException<'T>(e)) + + let FromCanceled<'T> (ct : CancellationToken) = + ValueTask<'T>(Task.FromCanceled<'T>(ct)) + + let FromTask<'T> (t : Task<'T>) = + ValueTask<'T>(t) + + /// Creates a ValueTask workflow from 'source' another, mapping its result with 'f'. + let map (f: 'T -> 'U) (source: ValueTask<'T>) : ValueTask<'U> = + backgroundTask { + let! r = source + return f r + } |> ValueTask<'U> + + /// Creates a ValueTask workflow from two workflows 'x' and 'y', mapping its results with 'f'. + /// Workflows are run in sequence. + /// The mapping function. + /// First ValueTask workflow. + /// Second ValueTask workflow. + let map2 (f: 'T -> 'U -> 'V) (x: ValueTask<'T>) (y: ValueTask<'U>) : ValueTask<'V> = + backgroundTask { + let! rX = x + let! rY = y + return f rX rY + } |> ValueTask<'V> + + /// Creates a ValueTask workflow from three workflows 'x', 'y' and z, mapping its results with 'f'. + /// Workflows are run in sequence. + /// The mapping function. + /// First ValueTask workflow. + /// Second ValueTask workflow. + /// Third ValueTask workflow. + let map3 (f : 'T -> 'U -> 'V -> 'W) (x : ValueTask<'T>) (y : ValueTask<'U>) (z: ValueTask<'V>) : ValueTask<'W> = + backgroundTask { + let! rX = x + let! rY = y + let! rZ = z + return f rX rY rZ + } |> ValueTask<'W> + + /// Creates a ValueTask workflow that is the result of applying the resulting function of a ValueTask workflow + /// to the resulting value of another ValueTask workflow + /// ValueTask workflow returning a function + /// ValueTask workflow returning a value + let apply (f: ValueTask<'T->'U>) (x: ValueTask<'T>) : ValueTask<'U> = + backgroundTask { + let! r = x + let! fn = f + return (fn r) + } |> ValueTask<'U> + + /// Creates a ValueTask workflow from two workflows 'x' and 'y', tupling its results. + let zip (x: ValueTask<'T>) (y: ValueTask<'U>) : ValueTask<'T * 'U> = + backgroundTask { + let! rX = x + let! rY = y + return (rX, rY) + } |> ValueTask<'T * 'U> + + /// Flattens two nested ValueTask into one. + let join (source: ValueTask>) : ValueTask<'T> = + backgroundTask { + let! s = source + return! s + } |> ValueTask<'T> + + + /// Creates a ValueTask workflow from 'source' workflow, mapping and flattening its result with 'f'. + let bind (f: 'T -> ValueTask<'U>) (source: ValueTask<'T>) : ValueTask<'U> = + source + |> map f + |> join + + /// Creates a ValueTask that ignores the result of the source ValueTask. + /// It can be used to convert non-generic ValueTask to unit ValueTask. + let ignore (source: ValueTask<'T>) = + backgroundTask { + let! _ = source + return () + } |> ValueTask + + + /// Raises an exception in the ValueTask + let raise (e: exn) = + FromException e + +#endif \ No newline at end of file diff --git a/src/FSharpPlus/FSharpPlus.fsproj b/src/FSharpPlus/FSharpPlus.fsproj index 139e9b253..775061300 100644 --- a/src/FSharpPlus/FSharpPlus.fsproj +++ b/src/FSharpPlus/FSharpPlus.fsproj @@ -5,7 +5,6 @@ $(FSC_ExePathCompilerBuild) - netstandard2.0 FSharpPlus FSharpPlus $(VersionPrefix).0 @@ -25,6 +24,7 @@ 6.0 $(DefineConstants);FABLE_COMPILER $(DefineConstants);FABLE_COMPILER;FABLE_COMPILER_3 + netstandard2.0;netstandard2.1 @@ -49,6 +49,7 @@ + diff --git a/tests/FSharpPlus.Tests/FSharpPlus.Tests.fsproj b/tests/FSharpPlus.Tests/FSharpPlus.Tests.fsproj index 7928280b8..d842f6d24 100644 --- a/tests/FSharpPlus.Tests/FSharpPlus.Tests.fsproj +++ b/tests/FSharpPlus.Tests/FSharpPlus.Tests.fsproj @@ -23,6 +23,7 @@ + diff --git a/tests/FSharpPlus.Tests/ValueTask.fs b/tests/FSharpPlus.Tests/ValueTask.fs new file mode 100644 index 000000000..0593b605a --- /dev/null +++ b/tests/FSharpPlus.Tests/ValueTask.fs @@ -0,0 +1,92 @@ +namespace FSharpPlus.Tests + + +module ValueTask = + + open System + open System.Threading.Tasks + open NUnit.Framework + open FSharpPlus + open FSharpPlus.Data + open FSharpPlus.Tests.Helpers + + exception TestException of string + + module ValueTaskTests = + + let createValueTask isFailed value = + if not isFailed then ValueTask.FromResult value + else + ValueTask.FromException (TestException (sprintf "Ouch, can't create: %A" value )) + + let (|AggregateException|_|) (x: exn) = + match x with + | :? AggregateException as e -> e.InnerExceptions |> Seq.toList |> Some + | _ -> None + + let require x msg = if not x then failwith msg + + [] + let shortCircuits () = + let x1 = createValueTask false 1 + let x2 = createValueTask false 2 + let x3 = createValueTask false 3 + + let a = ValueTask.map string x1 + require a.IsCompleted "ValueTask.map didn't short-circuit" + areEqual a.Result "1" + + let b = ValueTask.zip x1 x2 + require b.IsCompleted "ValueTask.zip didn't short-circuit" + let b1, b2 = b.Result + areEqual b1 1 + areEqual b2 2 + + let c = ValueTask.map2 (+) x1 x2 + require c.IsCompleted "ValueTask.map2 didn't short-circuit" + areEqual c.Result 3 + + let d = ValueTask.map3 (fun x y z -> x + y + z) x1 x2 x3 + require d.IsCompleted "ValueTask.map3 didn't short-circiut" + areEqual d.Result 6 + + [] + let erroredValueTasks () = + let e1 () = createValueTask true 1 + let e2 () = createValueTask true 2 + let e3 () = createValueTask true 3 + let x1 () = createValueTask false 1 + let x2 () = createValueTask false 2 + + let mapping isFailure x = if isFailure then raise (TestException "I was told to fail") else x + let mapping2 isFailure x y = if isFailure then raise (TestException "I was told to fail") else x + y + let mapping3 isFailure x y z = if isFailure then raise (TestException "I was told to fail") else x + y + z + let binding isFailure x = if isFailure then raise (TestException "I was told to fail") else ValueTask.FromResult (x + 10) + + let r01 = ValueTask.map (mapping false) (e1 ()) + r01.AsTask().Exception.InnerExceptions |> areEquivalent [TestException "Ouch, can't create: 1"] + + let r02 = ValueTask.map (mapping true) (x1 ()) + r02.AsTask().Exception.InnerExceptions |> areEquivalent [TestException "I was told to fail"] + + let r03 = ValueTask.zip (e1 ()) (x2 ()) + r03.AsTask().Exception.InnerExceptions |> areEquivalent [TestException "Ouch, can't create: 1"] + + let r04 = ValueTask.zip (e1 ()) (e2 ()) + r04.AsTask().Exception.InnerExceptions |> areEquivalent [TestException "Ouch, can't create: 1"] + + let r05 = ValueTask.map2 (mapping2 false) (e1 ()) (x2 ()) + r05.AsTask().Exception.InnerExceptions |> areEquivalent [TestException "Ouch, can't create: 1"] + + let r06 = ValueTask.map3 (mapping3 false) (e1 ()) (e2 ()) (e3 ()) + r06.AsTask().Exception.InnerExceptions |> areEquivalent [TestException "Ouch, can't create: 1"] + + let r07 = ValueTask.map3 (mapping3 false) (x1 ()) (e2 ()) (e3 ()) + r07.AsTask().Exception.InnerExceptions |> areEquivalent [TestException "Ouch, can't create: 2"] + + let r08 = ValueTask.bind (binding true) (e1 ()) + r08.AsTask().Exception.InnerExceptions |> areEquivalent [TestException "Ouch, can't create: 1"] + + let r09 = ValueTask.bind (binding true) (x1 ()) + r09.AsTask().Exception.InnerExceptions |> areEquivalent [TestException "I was told to fail"] + diff --git a/tests/FSharpPlusFable.Tests/FSharpPlusFable.Tests.fsproj b/tests/FSharpPlusFable.Tests/FSharpPlusFable.Tests.fsproj index f091059e2..b84021a05 100644 --- a/tests/FSharpPlusFable.Tests/FSharpPlusFable.Tests.fsproj +++ b/tests/FSharpPlusFable.Tests/FSharpPlusFable.Tests.fsproj @@ -1,14 +1,15 @@  - - Exe - Debug;Release;Fable;Fable3 - AnyCPU - 6.0 - $(DefineConstants);FABLE_COMPILER;FABLE_COMPILER_FAKE - $(DefineConstants);FABLE_COMPILER;FABLE_COMPILER_3;FABLE_COMPILER_FAKE - net6.0 - + + Exe + Debug;Release;Fable;Fable3 + AnyCPU + 6.0 + $(DefineConstants);FABLE_COMPILER;FABLE_COMPILER_FAKE + $(DefineConstants);FABLE_COMPILER;FABLE_COMPILER_3;FABLE_COMPILER_FAKE + net6.0 + +