diff --git a/README.md b/README.md index 8297cbd9..f11f6989 100644 --- a/README.md +++ b/README.md @@ -34,12 +34,10 @@ GitHub Actions | | FsToolkit.ErrorHandling.AsyncSeq | [![NuGet](https://buildstats.info/nuget/FsToolkit.ErrorHandling.AsyncSeq)](https://www.nuget.org/packages/FsToolkit.ErrorHandling.AsyncSeq) | [![NuGet](https://buildstats.info/nuget/FsToolkit.ErrorHandling.AsyncSeq?includePreReleases=true)](https://www.nuget.org/packages/FsToolkit.ErrorHandling.AsyncSeq/absoluteLatest) | FsToolkit.ErrorHandling.IcedTasks | [![NuGet](https://buildstats.info/nuget/FsToolkit.ErrorHandling.IcedTasks)](https://www.nuget.org/packages/FsToolkit.ErrorHandling.IcedTasks) | [![NuGet](https://buildstats.info/nuget/FsToolkit.ErrorHandling.IcedTasks?includePreReleases=true)](https://www.nuget.org/packages/FsToolkit.ErrorHandling.IcedTasks/absoluteLatest) - - ## Developing locally ### Devcontainer -This repository has a devcontainer setup for VSCode. For more infomation see: +This repository has a devcontainer setup for VSCode. For more information see: - [VSCode](https://code.visualstudio.com/docs/devcontainers/containers) ### Local Setup @@ -79,7 +77,7 @@ Without specifying a build target, the default target is `DotnetPack`, which wil - `RunTests` - Will run tests for `dotnet`, `fable-javascript` and `fable-python` projects - `FormatCode` - Will run `fantomas` to format the codebase -This is not an exhausting list. Additional targets can be found in the `./build/build.fs` file. +This is not an exhaustive list. Additional targets can be found in the `./build/build.fs` file. A motivating example diff --git a/src/FsToolkit.ErrorHandling/Seq.fs b/src/FsToolkit.ErrorHandling/Seq.fs index 021da617..959caf81 100644 --- a/src/FsToolkit.ErrorHandling/Seq.fs +++ b/src/FsToolkit.ErrorHandling/Seq.fs @@ -1,56 +1,60 @@ -// See here for previous design disccusions: +// See here for previous design discussions: // 1. https://github.com/demystifyfp/FsToolkit.ErrorHandling/pull/277 +// 2. https://github.com/demystifyfp/FsToolkit.ErrorHandling/pull/310 [] module FsToolkit.ErrorHandling.Seq +open Microsoft.FSharp.Core.CompilerServices + /// /// Applies a function to each element of a sequence and returns a single result /// /// The initial state /// The function to apply to each element /// The input sequence -/// A result with the ok elements in a sequence or the first error occurring in the sequence +/// If state and all elements in 'sequence' are Ok, a result with the elements in an array. Alternately, the first error encountered let inline traverseResultM' - state + (state: Result<'okOutput seq, 'error>) ([] f: 'okInput -> Result<'okOutput, 'error>) (xs: 'okInput seq) - = + : Result<'okOutput[], 'error> = + if isNull xs then + nullArg (nameof xs) + match state with - | Error _ -> state - | Ok oks -> - use enumerator = xs.GetEnumerator() + | Error e -> Error e + | Ok initialSuccesses -> - let rec loop oks = - if enumerator.MoveNext() then - match f enumerator.Current with - | Ok ok -> - loop ( - seq { - yield ok - yield! oks - } - ) - | Error e -> Error e - else - Ok(Seq.rev oks) + let oks = ResizeArray(initialSuccesses) + let mutable err = Unchecked.defaultof<'error> + let mutable ok = true + use e = xs.GetEnumerator() + + while ok + && e.MoveNext() do + match f e.Current with + | Ok r -> oks.Add r + | Error e -> + err <- e + ok <- false - loop oks + if ok then Ok(oks.ToArray()) else Error err /// /// Applies a function to each element of a sequence and returns a single result /// /// The function to apply to each element /// The input sequence -/// A result with the ok elements in a sequence or the first error occurring in the sequence -/// This function is equivalent to but applying and initial state of 'Seq.empty' +/// A result with the ok elements in an array, or the first error occurring in the sequence +/// This function is equivalent to but applying an initial state of 'Seq.empty' let traverseResultM f xs = traverseResultM' (Ok Seq.empty) f xs /// /// Converts a sequence of results into a single result /// /// The input sequence -/// A result with the ok elements in a sequence or the first error occurring in the sequence +/// A result bearing all the results as an array, or the first error occurring in the sequence /// This function is equivalent to but auto-applying the 'id' function let sequenceResultM xs = traverseResultM id xs @@ -60,45 +64,56 @@ let sequenceResultM xs = traverseResultM id xs /// The initial state /// The function to apply to each element /// The input sequence -/// A result with the ok elements in a sequence or a sequence of all errors occuring in the original sequence -let inline traverseResultA' state ([] f: 'okInput -> Result<'okOutput, 'error>) xs = - let folder state x = - match state, f x with - | Error errors, Error e -> - seq { - e - yield! Seq.rev errors - } - |> Seq.rev - |> Error - | Ok oks, Ok ok -> - seq { - ok - yield! Seq.rev oks - } - |> Seq.rev - |> Ok - | Ok _, Error e -> - Seq.singleton e - |> Error - | Error _, Ok _ -> state - - Seq.fold folder state xs +/// If no Errors encountered, an Ok result bearing an array of the ok elements from the 'state' followed by those gathered from the sequence, or an Error bearing an array of all errors from the 'state' and/or those in the sequence +let inline traverseResultA' + (state: Result<'okOutput seq, 'error seq>) + ([] f: 'okInput -> Result<'okOutput, 'error>) + xs + = + + if isNull xs then + nullArg (nameof xs) + + match state with + | Error failuresToDate -> + let errs = ResizeArray failuresToDate + + for x in xs do + match f x with + | Error e -> errs.Add e + | Ok _ -> () // as the initial state was failure, oks are irrelevant + + Error(errs.ToArray()) + | Ok initialSuccesses -> + + let oks = ResizeArray initialSuccesses + let errs = ResizeArray() + + for x in xs do + match f x with + | Ok r when errs.Count = 0 -> oks.Add r + | Ok _ -> () // no point saving results we won't use given the end result will be Error + | Error e -> errs.Add e + + if errs.Count = 0 then + Ok(oks.ToArray()) + else + Error(errs.ToArray()) /// /// Applies a function to each element of a sequence and returns a single result /// /// The function to apply to each element /// The input sequence -/// A result with the ok elements in a sequence or a sequence of all errors occuring in the original sequence -/// This function is equivalent to but applying and initial state of 'Seq.empty' +/// A result with the ok elements in an array or an array of all errors from the sequence +/// This function is equivalent to but applying an initial state of Seq.empty' let traverseResultA f xs = traverseResultA' (Ok Seq.empty) f xs /// /// Converts a sequence of results into a single result /// /// The input sequence -/// A result with the ok elements in a sequence or a sequence of all errors occuring in the original sequence +/// A result with the elements in an array or an array of all errors from the sequence /// This function is equivalent to but auto-applying the 'id' function let sequenceResultA xs = traverseResultA id xs @@ -108,36 +123,33 @@ let sequenceResultA xs = traverseResultA id xs /// The initial state /// The function to apply to each element /// The input sequence -/// An async result with the ok elements in a sequence or the first error occurring in the sequence +/// An async result with the ok elements in an array or the first error encountered in the state or the sequence let inline traverseAsyncResultM' - state + (state: Async>) ([] f: 'okInput -> Async>) (xs: 'okInput seq) - = + : Async> = + if isNull xs then + nullArg (nameof xs) + async { match! state with - | Error _ -> return! state - | Ok oks -> - use enumerator = xs.GetEnumerator() - - let rec loop oks = - async { - if enumerator.MoveNext() then - match! f enumerator.Current with - | Ok ok -> - return! - loop ( - seq { - yield ok - yield! oks - } - ) - | Error e -> return Error e - else - return Ok(Seq.rev oks) - } - - return! loop oks + | Error e -> return Error e + | Ok initialSuccesses -> + let oks = ResizeArray initialSuccesses + let mutable err = Unchecked.defaultof<'error> + let mutable ok = true + use e = xs.GetEnumerator() + + while ok + && e.MoveNext() do + match! f e.Current with + | Ok r -> oks.Add r + | Error e -> + err <- e + ok <- false + + return if ok then Ok(oks.ToArray()) else Error err } /// @@ -145,8 +157,8 @@ let inline traverseAsyncResultM' /// /// The function to apply to each element /// The input sequence -/// An async result with the ok elements in a sequence or the first error occurring in the sequence -/// This function is equivalent to but applying and initial state of 'Seq.empty' +/// An async result with the ok elements in an array or the first error occurring in the sequence +/// This function is equivalent to but applying an initial state of 'Seq.empty' let traverseAsyncResultM f xs = traverseAsyncResultM' (async { return Ok Seq.empty }) f xs @@ -154,58 +166,116 @@ let traverseAsyncResultM f xs = /// Converts a sequence of async results into a single async result /// /// The input sequence -/// An async result with the ok elements in a sequence or the first error occurring in the sequence +/// An async result with the ok elements in an array or the first error occurring in the sequence /// This function is equivalent to but auto-applying the 'id' function let sequenceAsyncResultM xs = traverseAsyncResultM id xs +#if !FABLE_COMPILER +/// +/// Applies a function to each element of a sequence and returns a single Task result +/// +/// The initial state +/// The function to apply to each element +/// The input sequence +/// A task result with the ok elements in an array or the first error encountered in the state or the sequence +let inline traverseTaskResultM' + (state: TaskResult<'okOutput seq, 'error>) + ([] f: 'okInput -> TaskResult<'okOutput, 'error>) + (xs: 'okInput seq) + : TaskResult<'okOutput[], 'error> = + if isNull xs then + nullArg (nameof xs) + + task { + match! state with + | Error e -> return Error e + | Ok initialSuccesses -> + let oks = ResizeArray initialSuccesses + let mutable err = Unchecked.defaultof<'error> + let mutable ok = true + use e = xs.GetEnumerator() + + while ok + && e.MoveNext() do + match! f e.Current with + | Ok r -> oks.Add r + | Error e -> + err <- e + ok <- false + + return if ok then Ok(oks.ToArray()) else Error err + } + +/// +/// Applies a function to each element of a sequence and returns a single Task result +/// +/// The function to apply to each element +/// The input sequence +/// A task result with the ok elements in an array or the first error occurring in the sequence +/// This function is equivalent to but applying an initial state of 'Seq.empty' +let traverseTaskResultM f xs = + traverseTaskResultM' (TaskResult.ok Seq.empty) f xs + +/// +/// Converts a sequence of Task results into a single Task result +/// +/// The input sequence +/// A task result with the ok elements in an array or the first error occurring in the sequence +/// This function is equivalent to but auto-applying the 'id' function +let sequenceTaskResultM xs = traverseTaskResultM id xs + +#endif + /// /// Applies a function to each element of a sequence and returns a single async result /// /// The initial state /// The function to apply to each element /// The input sequence -/// An async result with the ok elements in a sequence or a sequence of all errors occuring in the original sequence +/// An async result with the ok elements in an array or an array of all errors from the state and the original sequence let inline traverseAsyncResultA' - state + (state: Async>) ([] f: 'okInput -> Async>) - xs - = - let folder state x = - async { - let! state = state - let! result = f x - - return - match state, result with - | Error errors, Error e -> - seq { - e - yield! Seq.rev errors - } - |> Seq.rev - |> Error - | Ok oks, Ok ok -> - seq { - ok - yield! Seq.rev oks - } - |> Seq.rev - |> Ok - | Ok _, Error e -> - Seq.singleton e - |> Error - | Error _, Ok _ -> state - } - - Seq.fold folder state xs + (xs: 'okInput seq) + : Async> = + if isNull xs then + nullArg (nameof xs) + + async { + match! state with + | Error failuresToDate -> + let errs = ResizeArray failuresToDate + + for x in xs do + match! f x with + | Ok _ -> () // as the initial state was failure, oks are irrelevant + | Error e -> errs.Add e + + return Error(errs.ToArray()) + | Ok initialSuccesses -> + + let oks = ResizeArray initialSuccesses + let errs = ResizeArray() + + for x in xs do + match! f x with + | Ok r when errs.Count = 0 -> oks.Add r + | Ok _ -> () // no point saving results we won't use given the end result will be Error + | Error e -> errs.Add e + + if errs.Count = 0 then + return Ok(oks.ToArray()) + else + return Error(errs.ToArray()) + } /// /// Applies a function to each element of a sequence and returns a single async result /// /// The function to apply to each element /// The input sequence -/// An async result with the ok elements in a sequence or a sequence of all errors occuring in the original sequence -/// This function is equivalent to but applying and initial state of 'Seq.empty' +/// An async result with the ok elements in an array or an array of all errors occuring in the sequence +/// This function is equivalent to but applying an initial state of 'Seq.empty' let traverseAsyncResultA f xs = traverseAsyncResultA' (async { return Ok Seq.empty }) f xs @@ -213,57 +283,121 @@ let traverseAsyncResultA f xs = /// Converts a sequence of async results into a single async result /// /// The input sequence -/// An async result with the ok elements in a sequence or a sequence of all errors occuring in the original sequence +/// An async result with all ok elements in an array or an error result with an array of all errors occuring in the sequence /// This function is equivalent to but auto-applying the 'id' function let sequenceAsyncResultA xs = traverseAsyncResultA id xs +#if !FABLE_COMPILER + +/// +/// Applies a function to each element of a sequence and returns a single task result +/// +/// The initial state +/// The function to apply to each element +/// The input sequence +/// A task result with the ok elements in an array or an array of all errors from the state and the original sequence +let inline traverseTaskResultA' + (state: TaskResult<'okOutput seq, 'error seq>) + ([] f: 'okInput -> TaskResult<'okOutput, 'error>) + (xs: 'okInput seq) + : TaskResult<'okOutput[], 'error[]> = + if isNull xs then + nullArg (nameof xs) + + task { + match! state with + | Error failuresToDate -> + let mutable errs = ArrayCollector() + errs.AddMany failuresToDate + + for x in xs do + match! f x with + | Ok _ -> () // as the initial state was failure, oks are irrelevant + | Error e -> errs.Add e + + return Error(errs.Close()) + | Ok initialSuccesses -> + + let mutable oks = ArrayCollector() + oks.AddMany initialSuccesses + let mutable ok = true + let mutable errs = ArrayCollector() + + for x in xs do + match! f x with + | Ok r when ok -> oks.Add r + | Ok _ -> () // no point saving results we won't use given the end result will be Error + | Error e -> + errs.Add e + ok <- false + + return if ok then Ok(oks.Close()) else Error(errs.Close()) + } + +/// +/// Applies a function to each element of a sequence and returns a single task result +/// +/// The function to apply to each element +/// The input sequence +/// A task result with the ok elements in an array or an array of all errors occuring in the sequence +/// This function is equivalent to but applying an initial state of 'Seq.empty' +let traverseTaskResultA f xs = + traverseTaskResultA' (TaskResult.ok Seq.empty) f xs + +/// +/// Converts a sequence of task results into a single task result +/// +/// The input sequence +/// A task result with all ok elements in an array or an error result with an array of all errors occuring in the sequence +/// This function is equivalent to but auto-applying the 'id' function +let sequenceTaskResultA xs = traverseTaskResultA id xs + +#endif + /// /// Applies a function to each element of a sequence and returns a single option /// /// The initial state /// The function to apply to each element /// The input sequence -/// An option containing Some sequence of elements or None if any of the function applications return None +/// An option containing Some array of elements or None if any of the function applications return None let inline traverseOptionM' - state + (state: seq<'okOutput> option) ([] f: 'okInput -> 'okOutput option) (xs: 'okInput seq) - = + : 'okOutput[] option = + if isNull xs then + nullArg (nameof xs) + match state with - | None -> state - | Some values -> + | None -> None + | Some initialValues -> + let values = ResizeArray initialValues + let mutable ok = true use enumerator = xs.GetEnumerator() - let rec loop values = - if enumerator.MoveNext() then - match f enumerator.Current with - | Some value -> - loop ( - seq { - yield value - yield! values - } - ) - | None -> None - else - Some(Seq.rev values) + while ok + && enumerator.MoveNext() do + match f enumerator.Current with + | Some value -> values.Add value + | None -> ok <- false - loop values + if ok then Some(values.ToArray()) else None /// /// Applies a function to each element of a sequence and returns a single option /// /// The function to apply to each element /// The input sequence -/// An option containing Some sequence of elements or None if any of the function applications return None -/// This function is equivalent to but applying and initial state of 'Seq.empty' +/// An option containing Some array of elements or None if any of the function applications return None +/// This function is equivalent to but applying an initial state of 'Seq.empty' let traverseOptionM f xs = traverseOptionM' (Some Seq.empty) f xs /// /// Converts a sequence of options into a single option /// /// The input sequence -/// An option containing Some sequence of elements or None if any of the function applications return None +/// An option containing Some array of elements or None if any of the function applications return None /// This function is equivalent to but auto-applying the 'id' function let sequenceOptionM xs = traverseOptionM id xs @@ -273,36 +407,30 @@ let sequenceOptionM xs = traverseOptionM id xs /// The initial state /// The function to apply to each element /// The input sequence -/// An async option containing Some sequence of elements or None if any of the function applications return None +/// An async option containing Some array of elements or None if any of the function applications return None let inline traverseAsyncOptionM' - state + (state: Async option>) ([] f: 'okInput -> Async<'okOutput option>) (xs: 'okInput seq) - = + : Async<'okOutput[] option> = + if isNull xs then + nullArg (nameof xs) + async { match! state with - | None -> return! state - | Some values -> + | None -> return None + | Some initialValues -> + let values = ResizeArray initialValues + let mutable ok = true use enumerator = xs.GetEnumerator() - let rec loop values = - async { - if enumerator.MoveNext() then - match! f enumerator.Current with - | Some value -> - return! - loop ( - seq { - yield value - yield! values - } - ) - | None -> return None - else - return Some(Seq.rev values) - } - - return! loop values + while ok + && enumerator.MoveNext() do + match! f enumerator.Current with + | Some value -> values.Add value + | None -> ok <- false + + return if ok then Some(values.ToArray()) else None } /// @@ -310,8 +438,8 @@ let inline traverseAsyncOptionM' /// /// The function to apply to each element /// The input sequence -/// An async option containing Some sequence of elements or None if any of the function applications return None -/// This function is equivalent to but applying and initial state of 'Async { return Some Seq.empty }' +/// An async option containing Some array of elements or None if any of the function applications return None +/// This function is equivalent to but applying an initial state of 'Async { return Some Seq.empty }' let traverseAsyncOptionM f xs = traverseAsyncOptionM' (async { return Some Seq.empty }) f xs @@ -319,7 +447,7 @@ let traverseAsyncOptionM f xs = /// Converts a sequence of async options into a single async option /// /// The input sequence -/// An async option containing Some sequence of elements or None if any of the function applications return None +/// An async option containing Some array of elements or None if any of the function applications return None /// This function is equivalent to but auto-applying the 'id' function let sequenceAsyncOptionM xs = traverseAsyncOptionM id xs @@ -331,40 +459,38 @@ let sequenceAsyncOptionM xs = traverseAsyncOptionM id xs /// The initial state /// The function to apply to each element /// The input sequence -/// A voption containing Some sequence of elements or None if any of the function applications return None +/// A voption containing an Array of elements or None if any of the function applications return None let inline traverseVOptionM' - state + (state: ValueOption<'okOutput seq>) ([] f: 'okInput -> 'okOutput voption) (xs: 'okInput seq) - = + : ValueOption<'okOutput[]> = + if isNull xs then + nullArg (nameof xs) + match state with - | ValueNone -> state - | ValueSome values -> + | ValueNone -> ValueNone + | ValueSome initialValues -> + let mutable values = ArrayCollector() + values.AddMany initialValues + let mutable ok = true use enumerator = xs.GetEnumerator() - let rec loop values = - if enumerator.MoveNext() then - match f enumerator.Current with - | ValueSome value -> - loop ( - seq { - yield value - yield! values - } - ) - | ValueNone -> ValueNone - else - ValueSome(Seq.rev values) + while ok + && enumerator.MoveNext() do + match f enumerator.Current with + | ValueSome value -> values.Add value + | ValueNone -> ok <- false - loop values + if ok then ValueSome(values.Close()) else ValueNone /// /// Applies a function to each element of a sequence and returns a single voption /// /// The function to apply to each element /// The input sequence -/// A voption containing Some sequence of elements or None if any of the function applications return None -/// This function is equivalent to but applying and initial state of 'ValueSome Seq.empty' +/// A voption containing Some array of elements or None if any of the function applications return None +/// This function is equivalent to but applying an initial state of 'ValueSome Seq.empty' let traverseVOptionM f xs = traverseVOptionM' (ValueSome Seq.empty) f xs @@ -372,7 +498,7 @@ let traverseVOptionM f xs = /// Converts a sequence of voptions into a single voption /// /// The input sequence -/// A voption containing Some sequence of elements or None if any of the function applications return None +/// A voption containing Some array of elements or None if any of the function applications return None /// This function is equivalent to but auto-applying the 'id' function let sequenceVOptionM xs = traverseVOptionM id xs diff --git a/tests/FsToolkit.ErrorHandling.Tests/Seq.fs b/tests/FsToolkit.ErrorHandling.Tests/Seq.fs index 2059337d..1a2cc202 100644 --- a/tests/FsToolkit.ErrorHandling.Tests/Seq.fs +++ b/tests/FsToolkit.ErrorHandling.Tests/Seq.fs @@ -29,13 +29,11 @@ let traverseResultMTests = let expected = Seq.map tweet tweets - |> Seq.toList + |> Seq.toArray let actual = Seq.traverseResultM Tweet.TryCreate tweets - let actual = - Expect.wantOk actual "Expected result to be Ok" - |> Seq.toList + let actual = Expect.wantOk actual "Expected result to be Ok" Expect.equal actual expected "Should have a sequence of valid tweets" @@ -72,13 +70,10 @@ let traverseOptionMTests = "Hola" } - let expected = Seq.toList tweets + let expected = Seq.toArray tweets let actual = Seq.traverseOptionM tryTweetOption tweets - let actual = - Expect.wantSome actual "Expected result to be Some" - |> Seq.toList - + let actual = Expect.wantSome actual "Expected result to be Some" Expect.equal actual expected "Should have a sequence of valid tweets" testCase "traverseOption with few invalid data" @@ -109,13 +104,11 @@ let sequenceResultMTests = let expected = Seq.map tweet tweets - |> Seq.toList + |> Seq.toArray let actual = Seq.sequenceResultM (Seq.map Tweet.TryCreate tweets) - let actual = - Expect.wantOk actual "Expected result to be Ok" - |> Seq.toList + let actual = Expect.wantOk actual "Expected result to be Ok" Expect.equal actual expected "Should have a sequence of valid tweets" @@ -185,12 +178,10 @@ let sequenceOptionMTests = "Hola" } - let expected = Seq.toList tweets + let expected = Seq.toArray tweets let actual = Seq.sequenceOptionM (Seq.map tryTweetOption tweets) - let actual = - Expect.wantSome actual "Expected result to be Some" - |> Seq.toList + let actual = Expect.wantSome actual "Expected result to be Some" Expect.equal actual expected "Should have a sequence of valid tweets" @@ -250,13 +241,11 @@ let traverseResultATests = let expected = Seq.map tweet tweets - |> Seq.toList + |> Seq.toArray let actual = Seq.traverseResultA Tweet.TryCreate tweets - let actual = - Expect.wantOk actual "Expected result to be Ok" - |> Seq.toList + let actual = Expect.wantOk actual "Expected result to be Ok" Expect.equal actual expected "Should have a sequence of valid tweets" @@ -271,14 +260,12 @@ let traverseResultATests = let actual = Seq.traverseResultA Tweet.TryCreate tweets - let actual = - Expect.wantError actual "Expected result to be Error" - |> Seq.toList + let actual = Expect.wantError actual "Expected result to be Error" - let expected = [ + let expected = [| emptyTweetErrMsg longerTweetErrMsg - ] + |] Expect.equal actual expected "traverse the sequence and return all the errors" ] @@ -296,13 +283,11 @@ let sequenceResultATests = let expected = Seq.map tweet tweets - |> Seq.toList + |> Seq.toArray let actual = Seq.sequenceResultA (Seq.map Tweet.TryCreate tweets) - let actual = - Expect.wantOk actual "Expected result to be Ok" - |> Seq.toList + let actual = Expect.wantOk actual "Expected result to be Ok" Expect.equal actual expected "Should have a sequence of valid tweets" @@ -317,14 +302,12 @@ let sequenceResultATests = let actual = Seq.sequenceResultA (Seq.map Tweet.TryCreate tweets) - let actual = - Expect.wantError actual "Expected result to be Error" - |> Seq.toList + let actual = Expect.wantError actual "Expected result to be Error" - let expected = [ + let expected = [| emptyTweetErrMsg longerTweetErrMsg - ] + |] Expect.equal actual expected "traverse the sequence and return all the errors" ] @@ -350,13 +333,11 @@ let traverseAsyncResultMTests = let expected = userIds |> Seq.map (fun (UserId user) -> (newPostId, user)) - |> Seq.toList + |> Seq.toArray let! actual = Seq.traverseAsyncResultM (notifyNewPostSuccess (PostId newPostId)) userIds - let actual = - Expect.wantOk actual "Expected result to be Ok" - |> Seq.toList + let actual = Expect.wantOk actual "Expected result to be Ok" Expect.equal actual expected "Should have a sequence of valid data" } @@ -372,6 +353,54 @@ let traverseAsyncResultMTests = } ] +#if !FABLE_COMPILER + +let traverseTaskResultMTests = + + let notifyNewPostSuccess (PostId post) (UserId user) = TaskResult.ok (post, user) + + let notifyNewPostFailure (PostId _) (UserId uId) = TaskResult.error $"error: %O{uId}" + + let userIds = + seq { + userId1 + userId2 + userId3 + } + |> Seq.map UserId + + testList "Seq.traverseTaskResultM Tests" [ + testCaseAsync "traverseTaskResultM with a sequence of valid data" + <| async { + let expected = + userIds + |> Seq.map (fun (UserId user) -> (newPostId, user)) + |> Seq.toArray + + let! actual = + Seq.traverseTaskResultM (notifyNewPostSuccess (PostId newPostId)) userIds + |> Async.AwaitTask + + let actual = Expect.wantOk actual "Expected result to be Ok" + + Expect.equal actual expected "Should have a sequence of valid data" + } + + testCaseAsync "traverseResultA with few invalid data" + <| async { + let expected = $"error: %O{userId1}" + + let actual = + Seq.traverseTaskResultM (notifyNewPostFailure (PostId newPostId)) userIds + + do! + Expect.hasTaskErrorValue expected actual + |> Async.AwaitTask + } + ] + +#endif + let traverseAsyncOptionMTests = let userIds = @@ -386,14 +415,12 @@ let traverseAsyncOptionMTests = <| async { let expected = userIds - |> Seq.toList + |> Seq.toArray |> Some let f x = async { return Some x } - let actual = - Seq.traverseAsyncOptionM f userIds - |> AsyncOption.map Seq.toList + let actual = Seq.traverseAsyncOptionM f userIds match expected with | Some e -> do! Expect.hasAsyncSomeValue e actual @@ -411,7 +438,63 @@ let traverseAsyncOptionMTests = | None -> do! Expect.hasAsyncNoneValue actual } ] +#if !FABLE_COMPILER + +let traverseTaskResultATests = + let notifyNewPostSuccess (PostId post) (UserId user) = TaskResult.ok (post, user) + + let notifyFailure (PostId _) (UserId uId) = + if + uId = userId1 + || uId = userId3 + then + TaskResult.error $"error: %O{uId}" + else + TaskResult.ok () + + let userIds = + seq { + userId1 + userId2 + userId3 + userId4 + } + |> Seq.map UserId + + testList "Seq.traverseTaskResultA Tests" [ + testCaseAsync "traverseTaskResultA with a sequence of valid data" + <| async { + let expected = + userIds + |> Seq.map (fun (UserId user) -> (newPostId, user)) + |> Seq.toArray + + let! actual = + Seq.traverseTaskResultA (notifyNewPostSuccess (PostId newPostId)) userIds + |> Async.AwaitTask + + let actual = Expect.wantOk actual "Expected result to be Ok" + Expect.equal actual expected "Should have a sequence of valid data" + } + + testCaseAsync "traverseResultA with few invalid data" + <| async { + let expected = [| + sprintf "error: %s" (userId1.ToString()) + sprintf "error: %s" (userId3.ToString()) + |] + + let! actual = + Seq.traverseTaskResultA (notifyFailure (PostId newPostId)) userIds + |> Async.AwaitTask + + let actual = Expect.wantError actual "Expected result to be Error" + + Expect.equal actual expected "Should have a sequence of errors" + } + ] +#endif let notifyFailure (PostId _) (UserId uId) = async { if @@ -425,6 +508,44 @@ let notifyFailure (PostId _) (UserId uId) = return Ok() } +let sequenceAsyncResultMTests = + let userIds = + seq { + userId1 + userId2 + userId3 + userId4 + } + |> Seq.map UserId + + testList "Seq.sequenceAsyncResultM Tests" [ + testCaseAsync "sequenceAsyncResultM with a sequence of valid data" + <| async { + let expected = + userIds + |> Seq.map (fun (UserId user) -> (newPostId, user)) + |> Seq.toArray + + let! actual = + Seq.map (notifyNewPostSuccess (PostId newPostId)) userIds + |> Seq.sequenceAsyncResultM + + let actual = Expect.wantOk actual "Expected result to be Ok" + + Expect.equal actual expected "Should have a sequence of valid data" + } + + testCaseAsync "sequenceAsyncResultM with few invalid data" + <| async { + let expected = sprintf "error: %s" (userId1.ToString()) + + let actual = + Seq.map (notifyFailure (PostId newPostId)) userIds + |> Seq.sequenceAsyncResultM + + do! Expect.hasAsyncErrorValue expected actual + } + ] let traverseAsyncResultATests = let userIds = @@ -442,36 +563,44 @@ let traverseAsyncResultATests = let expected = userIds |> Seq.map (fun (UserId user) -> (newPostId, user)) - |> Seq.toList + |> Seq.toArray let! actual = Seq.traverseAsyncResultA (notifyNewPostSuccess (PostId newPostId)) userIds - let actual = - Expect.wantOk actual "Expected result to be Ok" - |> Seq.toList + let actual = Expect.wantOk actual "Expected result to be Ok" Expect.equal actual expected "Should have a sequence of valid data" } testCaseAsync "traverseResultA with few invalid data" <| async { - let expected = [ + let expected = [| sprintf "error: %s" (userId1.ToString()) sprintf "error: %s" (userId3.ToString()) - ] + |] let! actual = Seq.traverseAsyncResultA (notifyFailure (PostId newPostId)) userIds - let actual = - Expect.wantError actual "Expected result to be Error" - |> Seq.toList + let actual = Expect.wantError actual "Expected result to be Error" Expect.equal actual expected "Should have a sequence of errors" } ] +#if !FABLE_COMPILER + +let sequenceTaskResultMTests = + let notifyNewPostSuccess (PostId post) (UserId user) = TaskResult.ok (post, user) + + let notifyFailure (PostId _) (UserId uId) = + if + uId = userId1 + || uId = userId3 + then + TaskResult.error $"error: %O{uId}" + else + TaskResult.ok () -let sequenceAsyncResultMTests = let userIds = seq { userId1 @@ -481,37 +610,40 @@ let sequenceAsyncResultMTests = } |> Seq.map UserId - testList "Seq.sequenceAsyncResultM Tests" [ - testCaseAsync "sequenceAsyncResultM with a sequence of valid data" + testList "Seq.sequenceTaskResultM Tests" [ + testCaseAsync "sequenceTaskResultM with a sequence of valid data" <| async { let expected = userIds |> Seq.map (fun (UserId user) -> (newPostId, user)) - |> Seq.toList + |> Seq.toArray let! actual = Seq.map (notifyNewPostSuccess (PostId newPostId)) userIds - |> Seq.sequenceAsyncResultM + |> Seq.sequenceTaskResultM + |> Async.AwaitTask - let actual = - Expect.wantOk actual "Expected result to be Ok" - |> Seq.toList + let actual = Expect.wantOk actual "Expected result to be Ok" Expect.equal actual expected "Should have a sequence of valid data" } - testCaseAsync "sequenceAsyncResultM with few invalid data" + testCaseAsync "sequenceTaskResultM with few invalid data" <| async { let expected = sprintf "error: %s" (userId1.ToString()) let actual = - Seq.map (notifyFailure (PostId newPostId)) userIds - |> Seq.sequenceAsyncResultM + userIds + |> Seq.map (notifyFailure (PostId newPostId)) + |> Seq.sequenceTaskResultM + |> Async.AwaitTask do! Expect.hasAsyncErrorValue expected actual } ] +#endif + let sequenceAsyncOptionMTests = let userIds = @@ -525,7 +657,7 @@ let sequenceAsyncOptionMTests = testCaseAsync "sequenceAsyncOptionM with a sequence of valid data" <| async { let expected = - Seq.toList userIds + Seq.toArray userIds |> Some let f x = async { return Some x } @@ -533,7 +665,6 @@ let sequenceAsyncOptionMTests = let actual = Seq.map f userIds |> Seq.sequenceAsyncOptionM - |> AsyncOption.map Seq.toList match expected with | Some e -> do! Expect.hasAsyncSomeValue e actual @@ -571,27 +702,25 @@ let sequenceAsyncResultATests = let expected = userIds |> Seq.map (fun (UserId user) -> (newPostId, user)) - |> Seq.toList + |> Seq.toArray let actual = Seq.map (notifyNewPostSuccess (PostId newPostId)) userIds |> Seq.sequenceAsyncResultA - |> AsyncResult.map Seq.toList do! Expect.hasAsyncOkValue expected actual } testCaseAsync "sequenceAsyncResultA with few invalid data" <| async { - let expected = [ + let expected = [| sprintf "error: %s" (userId1.ToString()) sprintf "error: %s" (userId3.ToString()) - ] + |] let! actual = Seq.map (notifyFailure (PostId newPostId)) userIds |> Seq.sequenceAsyncResultA - |> AsyncResult.mapError Seq.toList let actual = Expect.wantError actual "Expected result to be Error" Expect.equal actual expected "Should have a sequence of errors" @@ -599,6 +728,61 @@ let sequenceAsyncResultATests = ] #if !FABLE_COMPILER +let sequenceTaskResultATests = + let notifyNewPostSuccess (PostId post) (UserId user) = TaskResult.ok (post, user) + + let notifyFailure (PostId _) (UserId uId) = + if + uId = userId1 + || uId = userId3 + then + TaskResult.error $"error: %O{uId}" + else + TaskResult.ok () + + let userIds = + seq { + userId1 + userId2 + userId3 + userId4 + } + |> Seq.map UserId + + testList "Seq.sequenceTaskResultA Tests" [ + testCaseAsync "sequenceTaskResultA with a sequence of valid data" + <| async { + let expected = + userIds + |> Seq.map (fun (UserId user) -> (newPostId, user)) + |> Seq.toArray + + let actual = + Seq.map (notifyNewPostSuccess (PostId newPostId)) userIds + |> Seq.sequenceTaskResultA + + do! + Expect.hasTaskOkValue expected actual + |> Async.AwaitTask + } + + testCaseAsync "sequenceTaskResultA with few invalid data" + <| async { + let expected = [| + sprintf "error: %s" (userId1.ToString()) + sprintf "error: %s" (userId3.ToString()) + |] + + let! actual = + Seq.map (notifyFailure (PostId newPostId)) userIds + |> Seq.sequenceTaskResultA + |> Async.AwaitTask + + let actual = Expect.wantError actual "Expected result to be Error" + Expect.equal actual expected "Should have a sequence of errors" + } + ] + let traverseVOptionMTests = testList "Seq.traverseVOptionM Tests" [ let tryTweetVOption x = @@ -615,11 +799,9 @@ let traverseVOptionMTests = "Hola" } - let expected = Seq.toList tweets + let expected = Seq.toArray tweets - let actual = - Seq.traverseVOptionM tryTweetVOption tweets - |> ValueOption.map Seq.toList + let actual = Seq.traverseVOptionM tryTweetVOption tweets match actual with | ValueSome actual -> @@ -655,11 +837,9 @@ let sequenceVOptionMTests = "Hola" } - let expected = Seq.toList tweets + let expected = Seq.toArray tweets - let actual = - Seq.sequenceVOptionM (Seq.map tryTweetOption tweets) - |> ValueOption.map Seq.toList + let actual = Seq.sequenceVOptionM (Seq.map tryTweetOption tweets) match actual with | ValueSome actual -> @@ -713,7 +893,7 @@ let sequenceVOptionMTests = #endif let allTests = - testList "List Tests" [ + testList "Seq Tests" [ traverseResultMTests traverseOptionMTests sequenceResultMTests @@ -721,12 +901,20 @@ let allTests = traverseResultATests sequenceResultATests traverseAsyncResultMTests +#if !FABLE_COMPILER + traverseTaskResultMTests +#endif traverseAsyncOptionMTests traverseAsyncResultATests sequenceAsyncResultMTests +#if !FABLE_COMPILER + traverseTaskResultATests + sequenceTaskResultMTests +#endif sequenceAsyncOptionMTests sequenceAsyncResultATests #if !FABLE_COMPILER + sequenceTaskResultATests traverseVOptionMTests sequenceVOptionMTests #endif