diff --git a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Concat.Tests.fs b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Concat.Tests.fs index 352aa4c..eefef38 100644 --- a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Concat.Tests.fs +++ b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Concat.Tests.fs @@ -1,5 +1,6 @@ module TaskSeq.Tests.Concat +open System open System.Collections.Generic open Xunit @@ -8,7 +9,11 @@ open FsUnit.Xunit open FSharp.Control // -// TaskSeq.concat +// TaskSeq.concat - of task seqs +// TaskSeq.concat - of seqs +// TaskSeq.concat - of lists +// TaskSeq.concat - of arrays +// TaskSeq.concat - of resizable arrays // let validateSequence ts = @@ -20,31 +25,126 @@ let validateSequence ts = module EmptySeq = [] - let ``Null source is invalid`` () = assertNullArg <| fun () -> TaskSeq.concat null + let ``Null source is invalid (taskseq)`` () = + assertNullArg + <| fun () -> TaskSeq.concat (null: TaskSeq>) + + [] + let ``Null source is invalid (seq)`` () = + assertNullArg + <| fun () -> TaskSeq.concat (null: TaskSeq>) + + [] + let ``Null source is invalid (array)`` () = + assertNullArg + <| fun () -> TaskSeq.concat (null: TaskSeq>) + + [] + let ``Null source is invalid (list)`` () = + assertNullArg + <| fun () -> TaskSeq.concat (null: TaskSeq>) + + [] + let ``Null source is invalid (resizarray)`` () = + assertNullArg + <| fun () -> TaskSeq.concat (null: TaskSeq>) [)>] - let ``TaskSeq-concat with empty sequences`` variant = + let ``TaskSeq-concat with nested empty task sequences`` variant = taskSeq { - yield Gen.getEmptyVariant variant // not yield-bang! yield Gen.getEmptyVariant variant yield Gen.getEmptyVariant variant + yield Gen.getEmptyVariant variant + } + |> TaskSeq.concat + |> verifyEmpty + + [] + let ``TaskSeq-concat with nested empty sequences`` () = + taskSeq { + yield Seq.empty + yield Seq.empty + yield Seq.empty + } + |> TaskSeq.concat + |> verifyEmpty + + [] + let ``TaskSeq-concat with nested empty arrays`` () = + taskSeq { + yield Array.empty + yield Array.empty + yield Array.empty + } + |> TaskSeq.concat + |> verifyEmpty + + [] + let ``TaskSeq-concat with nested empty lists`` () = + taskSeq { + yield List.empty + yield List.empty + yield List.empty } |> TaskSeq.concat |> verifyEmpty + [] + let ``TaskSeq-concat with multiple nested empty resizable arrays`` () = + taskSeq { + yield ResizeArray(List.empty) + yield ResizeArray(List.empty) + yield ResizeArray(List.empty) + } + |> TaskSeq.concat + |> verifyEmpty + + [)>] + let ``TaskSeq-concat with empty source (taskseq)`` variant = + Gen.getEmptyVariant variant + |> TaskSeq.box + |> TaskSeq.cast> // task seq is empty so this should not raise + |> TaskSeq.concat + |> verifyEmpty + + [)>] + let ``TaskSeq-concat with empty source (seq)`` variant = + Gen.getEmptyVariant variant + |> TaskSeq.box + |> TaskSeq.cast // task seq is empty so this should not raise + |> TaskSeq.concat + |> verifyEmpty + [)>] - let ``TaskSeq-concat with top sequence empty`` variant = + let ``TaskSeq-concat with empty source (list)`` variant = Gen.getEmptyVariant variant |> TaskSeq.box - |> TaskSeq.cast> // casting an int to an enumerable, LOL! + |> TaskSeq.cast // task seq is empty so this should not raise |> TaskSeq.concat |> verifyEmpty + [)>] + let ``TaskSeq-concat with empty source (array)`` variant = + Gen.getEmptyVariant variant + |> TaskSeq.box + |> TaskSeq.cast // task seq is empty so this should not raise + |> TaskSeq.concat + |> verifyEmpty + + [)>] + let ``TaskSeq-concat with empty source (resizearray)`` variant = + Gen.getEmptyVariant variant + |> TaskSeq.box + |> TaskSeq.cast> // task seq is empty so this should not raise + |> TaskSeq.concat + |> verifyEmpty + + module Immutable = [)>] let ``TaskSeq-concat with three sequences of sequences`` variant = taskSeq { - yield Gen.getSeqImmutable variant // not yield-bang! + yield Gen.getSeqImmutable variant yield Gen.getSeqImmutable variant yield Gen.getSeqImmutable variant } @@ -55,7 +155,7 @@ module Immutable = let ``TaskSeq-concat with three sequences of sequences and few empties`` variant = taskSeq { yield TaskSeq.empty - yield Gen.getSeqImmutable variant // not yield-bang! + yield Gen.getSeqImmutable variant yield TaskSeq.empty yield TaskSeq.empty yield Gen.getSeqImmutable variant @@ -69,14 +169,26 @@ module Immutable = |> TaskSeq.concat |> validateSequence -module SideEffect = [)>] - let ``TaskSeq-concat consumes until the end, including side-effects`` variant = + let ``TaskSeq-concat throws when one of inner task sequence is null`` variant = + fun () -> + taskSeq { + yield Gen.getSeqImmutable variant + yield TaskSeq.empty + yield null + } + |> TaskSeq.concat + |> consumeTaskSeq + |> should throwAsyncExact typeof + +module SideEffect = + [] + let ``TaskSeq-concat executes side effects of nested (taskseq)`` () = let mutable i = 0 taskSeq { - yield Gen.getSeqImmutable variant // not yield-bang! - yield Gen.getSeqImmutable variant + yield Gen.getSeqImmutable SeqImmutable.ThreadSpinWait + yield Gen.getSeqImmutable SeqImmutable.ThreadSpinWait yield taskSeq { yield! [ 1..10 ] @@ -84,8 +196,73 @@ module SideEffect = } } |> TaskSeq.concat - |> validateSequence - |> Task.map (fun () -> i |> should equal 1) + |> TaskSeq.last // consume + |> Task.map (fun _ -> i |> should equal 1) + + [] + let ``TaskSeq-concat executes side effects of nested (seq)`` () = + let mutable i = 0 + + taskSeq { + yield seq { 1..10 } + yield seq { 1..10 } + + yield seq { + yield! [ 1..10 ] + i <- i + 1 + } + } + |> TaskSeq.concat + |> TaskSeq.last // consume + |> Task.map (fun _ -> i |> should equal 1) + + [] + let ``TaskSeq-concat executes side effects of nested (array)`` () = + let mutable i = 0 + + taskSeq { + yield [| 1..10 |] + yield [| 1..10 |] + + yield [| yield! [ 1..10 ]; i <- i + 1 |] + } + |> TaskSeq.concat + |> TaskSeq.last // consume + |> Task.map (fun _ -> i |> should equal 1) + + [] + let ``TaskSeq-concat executes side effects of nested (list)`` () = + let mutable i = 0 + + taskSeq { + yield [ 1..10 ] + yield [ 1..10 ] + + yield [ yield! [ 1..10 ]; i <- i + 1 ] + } + |> TaskSeq.concat + |> TaskSeq.last // consume + |> Task.map (fun _ -> i |> should equal 1) + + [] + let ``TaskSeq-concat executes side effects of nested (resizearray)`` () = + let mutable i = 0 + + taskSeq { + yield ResizeArray { 1..10 } + yield ResizeArray { 1..10 } + + yield + ResizeArray( + seq { + yield! [ 1..10 ] + i <- i + 1 + } + ) + } + |> TaskSeq.concat + |> TaskSeq.last // consume + |> Task.map (fun _ -> i |> should equal 1) [)>] let ``TaskSeq-concat consumes side effects in empty sequences`` variant = diff --git a/src/FSharp.Control.TaskSeq/TaskSeq.fs b/src/FSharp.Control.TaskSeq/TaskSeq.fs index 357a378..f944ef7 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeq.fs +++ b/src/FSharp.Control.TaskSeq/TaskSeq.fs @@ -191,6 +191,42 @@ type TaskSeq private () = yield! (ts :> TaskSeq<'T>) } + static member concat(sources: TaskSeq<'T seq>) = // NOTE: we cannot use flex types on two overloads + Internal.checkNonNull (nameof sources) sources + + taskSeq { + for ts in sources do + // no null-check of inner seqs, similar to seq + yield! ts + } + + static member concat(sources: TaskSeq<'T[]>) = + Internal.checkNonNull (nameof sources) sources + + taskSeq { + for ts in sources do + // no null-check of inner arrays, similar to seq + yield! ts + } + + static member concat(sources: TaskSeq<'T list>) = + Internal.checkNonNull (nameof sources) sources + + taskSeq { + for ts in sources do + // no null-check of inner lists, similar to seq + yield! ts + } + + static member concat(sources: TaskSeq>) = + Internal.checkNonNull (nameof sources) sources + + taskSeq { + for ts in sources do + // no null-check of inner resize arrays, similar to seq + yield! ts + } + static member append (source1: TaskSeq<'T>) (source2: TaskSeq<'T>) = Internal.checkNonNull (nameof source1) source1 Internal.checkNonNull (nameof source2) source2 diff --git a/src/FSharp.Control.TaskSeq/TaskSeq.fsi b/src/FSharp.Control.TaskSeq/TaskSeq.fsi index 5a3b16f..7b1ab5c 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeq.fsi +++ b/src/FSharp.Control.TaskSeq/TaskSeq.fsi @@ -215,14 +215,55 @@ type TaskSeq = /// /// Combines the given task sequence of task sequences and concatenates them end-to-end, to form a - /// new flattened, single task sequence. Each task sequence is awaited item by item, before the next is iterated. + /// new flattened, single task sequence, like . Each task sequence is + /// awaited and consumed in full, before the next one is iterated. /// /// /// The input task-sequence-of-task-sequences. - /// The resulting task sequence. + /// The resulting, flattened task sequence. /// Thrown when the input task sequence of task sequences is null. static member concat: sources: TaskSeq<#TaskSeq<'T>> -> TaskSeq<'T> + /// + /// Combines the given task sequence of sequences and concatenates them end-to-end, to form a + /// new flattened, single task sequence. + /// + /// + /// The input task sequence of sequences. + /// The resulting, flattened task sequence. + /// Thrown when the input task sequence of task sequences is null. + static member concat: sources: TaskSeq<'T seq> -> TaskSeq<'T> + + /// + /// Combines the given task sequence of arrays and concatenates them end-to-end, to form a + /// new flattened, single task sequence. + /// + /// + /// The input task sequence of arrays. + /// The resulting, flattened task sequence. + /// Thrown when the input task sequence of task sequences is null. + static member concat: sources: TaskSeq<'T[]> -> TaskSeq<'T> + + /// + /// Combines the given task sequence of lists and concatenates them end-to-end, to form a + /// new flattened, single task sequence. + /// + /// + /// The input task sequence of lists. + /// The resulting, flattened task sequence. + /// Thrown when the input task sequence of task sequences is null. + static member concat: sources: TaskSeq<'T list> -> TaskSeq<'T> + + /// + /// Combines the given task sequence of resizable arrays and concatenates them end-to-end, to form a + /// new flattened, single task sequence. + /// + /// + /// The input task sequence of resizable arrays. + /// The resulting, flattened task sequence. + /// Thrown when the input task sequence of task sequences is null. + static member concat: sources: TaskSeq> -> TaskSeq<'T> + /// /// Concatenates task sequences and in order as a single /// task sequence.