diff --git a/src/FSharpy.TaskSeq.Test/FSharpy.TaskSeq.Test.fsproj b/src/FSharpy.TaskSeq.Test/FSharpy.TaskSeq.Test.fsproj
index dd013994..2b309a55 100644
--- a/src/FSharpy.TaskSeq.Test/FSharpy.TaskSeq.Test.fsproj
+++ b/src/FSharpy.TaskSeq.Test/FSharpy.TaskSeq.Test.fsproj
@@ -13,6 +13,7 @@
+
@@ -22,6 +23,7 @@
+
diff --git a/src/FSharpy.TaskSeq.Test/TaskSeq.Choose.Tests.fs b/src/FSharpy.TaskSeq.Test/TaskSeq.Choose.Tests.fs
index c70bbd7e..32d1dde8 100644
--- a/src/FSharpy.TaskSeq.Test/TaskSeq.Choose.Tests.fs
+++ b/src/FSharpy.TaskSeq.Test/TaskSeq.Choose.Tests.fs
@@ -9,13 +9,6 @@ open FsToolkit.ErrorHandling
open FSharpy
-[]
-let ``ZHang timeout test`` () = task {
- let! empty = Task.Delay 30
-
- empty |> should be Null
-}
-
[]
let ``TaskSeq-choose on an empty sequence`` () = task {
let! empty =
@@ -50,7 +43,7 @@ let ``TaskSeq-choose can convert and filter`` () = task {
let ``TaskSeq-chooseAsync can convert and filter`` () = task {
let! alphabet =
createDummyTaskSeqWith 50L<µs> 1000L<µs> 50
- |> TaskSeq.choose (fun number -> if number <= 26 then Some(char number + '@') else None)
+ |> TaskSeq.chooseAsync (fun number -> task { return if number <= 26 then Some(char number + '@') else None })
|> TaskSeq.toArrayAsync
String alphabet |> should equal "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
diff --git a/src/FSharpy.TaskSeq.Test/TaskSeq.Collect.Tests.fs b/src/FSharpy.TaskSeq.Test/TaskSeq.Collect.Tests.fs
index 3d405e2d..1ba39520 100644
--- a/src/FSharpy.TaskSeq.Test/TaskSeq.Collect.Tests.fs
+++ b/src/FSharpy.TaskSeq.Test/TaskSeq.Collect.Tests.fs
@@ -6,6 +6,12 @@ open FsToolkit.ErrorHandling
open FSharpy
+let validateSequence sequence =
+ sequence
+ |> Seq.map string
+ |> String.concat ""
+ |> should equal "ABBCCDDEEFFGGHHIIJJK"
+
[]
let ``TaskSeq-collect operates in correct order`` () = task {
let! sq =
@@ -16,10 +22,22 @@ let ``TaskSeq-collect operates in correct order`` () = task {
})
|> TaskSeq.toSeqCachedAsync
- sq
- |> Seq.map string
- |> String.concat ""
- |> should equal "ABBCCDDEEFFGGHHIIJJK"
+ validateSequence sq
+}
+
+[]
+let ``TaskSeq-collectAsync operates in correct order`` () = task {
+ let! sq =
+ createDummyTaskSeq 10
+ |> TaskSeq.collectAsync (fun item -> task {
+ return taskSeq {
+ yield char (item + 64)
+ yield char (item + 65)
+ }
+ })
+ |> TaskSeq.toSeqCachedAsync
+
+ validateSequence sq
}
[]
@@ -32,10 +50,42 @@ let ``TaskSeq-collectSeq operates in correct order`` () = task {
})
|> TaskSeq.toSeqCachedAsync
- sq
- |> Seq.map string
- |> String.concat ""
- |> should equal "ABBCCDDEEFFGGHHIIJJK"
+ validateSequence sq
+}
+
+[]
+let ``TaskSeq-collectSeq with arrays operates in correct order`` () = task {
+ let! sq =
+ createDummyTaskSeq 10
+ |> TaskSeq.collectSeq (fun item -> [| char (item + 64); char (item + 65) |])
+ |> TaskSeq.toArrayAsync
+
+ validateSequence sq
+}
+
+[]
+let ``TaskSeq-collectSeqAsync operates in correct order`` () = task {
+ let! sq =
+ createDummyTaskSeq 10
+ |> TaskSeq.collectSeqAsync (fun item -> task {
+ return seq {
+ yield char (item + 64)
+ yield char (item + 65)
+ }
+ })
+ |> TaskSeq.toSeqCachedAsync
+
+ validateSequence sq
+}
+
+[]
+let ``TaskSeq-collectSeqAsync with arrays operates in correct order`` () = task {
+ let! sq =
+ createDummyTaskSeq 10
+ |> TaskSeq.collectSeqAsync (fun item -> task { return [| char (item + 64); char (item + 65) |] })
+ |> TaskSeq.toArrayAsync
+
+ validateSequence sq
}
[]
@@ -48,6 +98,16 @@ let ``TaskSeq-collect with empty task sequences`` () = task {
Seq.isEmpty sq |> should be True
}
+[]
+let ``TaskSeq-collectAsync with empty task sequences`` () = task {
+ let! sq =
+ createDummyTaskSeq 10
+ |> TaskSeq.collectAsync (fun _ -> task { return TaskSeq.empty })
+ |> TaskSeq.toSeqCachedAsync
+
+ Seq.isEmpty sq |> should be True
+}
+
[]
let ``TaskSeq-collectSeq with empty sequences`` () = task {
let! sq =
@@ -59,7 +119,11 @@ let ``TaskSeq-collectSeq with empty sequences`` () = task {
}
[]
-let ``TaskSeq-empty is empty`` () = task {
- let! sq = TaskSeq.empty |> TaskSeq.toSeqCachedAsync
+let ``TaskSeq-collectSeqAsync with empty sequences`` () = task {
+ let! sq =
+ createDummyTaskSeq 10
+ |> TaskSeq.collectSeqAsync (fun _ -> task { return Array.empty })
+ |> TaskSeq.toSeqCachedAsync
+
Seq.isEmpty sq |> should be True
}
diff --git a/src/FSharpy.TaskSeq.Test/TaskSeq.ExactlyOne.Tests.fs b/src/FSharpy.TaskSeq.Test/TaskSeq.ExactlyOne.Tests.fs
new file mode 100644
index 00000000..f11ad2ac
--- /dev/null
+++ b/src/FSharpy.TaskSeq.Test/TaskSeq.ExactlyOne.Tests.fs
@@ -0,0 +1,57 @@
+module FSharpy.Tests.Head
+
+open System
+open Xunit
+open FsUnit.Xunit
+open FsToolkit.ErrorHandling
+
+open FSharpy
+
+
+[]
+let ``TaskSeq-head throws on empty sequences`` () = task {
+ fun () -> TaskSeq.empty |> TaskSeq.head |> Task.ignore
+ |> should throwAsyncExact typeof
+}
+
+[]
+let ``TaskSeq-head throws on empty sequences - variant`` () = task {
+ fun () -> taskSeq { do () } |> TaskSeq.head |> Task.ignore
+ |> should throwAsyncExact typeof
+}
+
+[]
+let ``TaskSeq-tryHead returns None on empty sequences`` () = task {
+ let! nothing = TaskSeq.empty |> TaskSeq.tryHead
+ nothing |> should be None'
+}
+
+[]
+let ``TaskSeq-head gets the first item in a longer sequence`` () = task {
+ let! head = createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 |> TaskSeq.head
+
+ head |> should equal 1
+}
+
+[]
+let ``TaskSeq-head gets the only item in a singleton sequence`` () = task {
+ let! head = taskSeq { yield 10 } |> TaskSeq.head
+ head |> should equal 10
+}
+
+[]
+let ``TaskSeq-tryHead gets the first item in a longer sequence`` () = task {
+ let! head =
+ createDummyTaskSeqWith 50L<µs> 1000L<µs> 50
+ |> TaskSeq.tryHead
+
+ head |> should be Some'
+ head |> should equal (Some 1)
+}
+
+[]
+let ``TaskSeq-tryHead gets the only item in a singleton sequence`` () = task {
+ let! head = taskSeq { yield 10 } |> TaskSeq.tryHead
+ head |> should be Some'
+ head |> should equal (Some 10)
+}
diff --git a/src/FSharpy.TaskSeq.Test/TaskSeq.Head.Tests.fs b/src/FSharpy.TaskSeq.Test/TaskSeq.Head.Tests.fs
index f11ad2ac..6377e63d 100644
--- a/src/FSharpy.TaskSeq.Test/TaskSeq.Head.Tests.fs
+++ b/src/FSharpy.TaskSeq.Test/TaskSeq.Head.Tests.fs
@@ -1,4 +1,4 @@
-module FSharpy.Tests.Head
+module FSharpy.Tests.ExactlyOne
open System
open Xunit
@@ -9,49 +9,91 @@ open FSharpy
[]
-let ``TaskSeq-head throws on empty sequences`` () = task {
- fun () -> TaskSeq.empty |> TaskSeq.head |> Task.ignore
+let ``TaskSeq-exactlyOne throws on empty sequences`` () = task {
+ fun () -> TaskSeq.empty |> TaskSeq.exactlyOne |> Task.ignore
|> should throwAsyncExact typeof
}
[]
-let ``TaskSeq-head throws on empty sequences - variant`` () = task {
- fun () -> taskSeq { do () } |> TaskSeq.head |> Task.ignore
+let ``TaskSeq-exactlyOne throws on empty sequences - variant`` () = task {
+ fun () -> taskSeq { do () } |> TaskSeq.exactlyOne |> Task.ignore
|> should throwAsyncExact typeof
}
[]
-let ``TaskSeq-tryHead returns None on empty sequences`` () = task {
- let! nothing = TaskSeq.empty |> TaskSeq.tryHead
+let ``TaskSeq-tryExactlyOne returns None on empty sequences`` () = task {
+ let! nothing = TaskSeq.empty |> TaskSeq.tryExactlyOne
nothing |> should be None'
}
[]
-let ``TaskSeq-head gets the first item in a longer sequence`` () = task {
- let! head = createDummyTaskSeqWith 50L<µs> 1000L<µs> 50 |> TaskSeq.head
+let ``TaskSeq-exactlyOne throws for a sequence of length = two`` () = task {
+ fun () ->
+ taskSeq {
+ yield 1
+ yield 2
+ }
+ |> TaskSeq.exactlyOne
+ |> Task.ignore
+ |> should throwAsyncExact typeof
+}
+
+[]
+let ``TaskSeq-exactlyOne throws for a sequence of length = two - variant`` () = task {
+ fun () ->
+ createDummyTaskSeqWith 50L<µs> 1000L<µs> 2
+ |> TaskSeq.exactlyOne
+ |> Task.ignore
+ |> should throwAsyncExact typeof
+}
+
+
+[]
+let ``TaskSeq-exactlyOne throws with a larger sequence`` () = task {
+ fun () ->
+ createDummyTaskSeqWith 50L<µs> 300L<µs> 200
+ |> TaskSeq.exactlyOne
+ |> Task.ignore
+ |> should throwAsyncExact typeof
+}
- head |> should equal 1
+[]
+let ``TaskSeq-tryExactlyOne returns None with a larger sequence`` () = task {
+ let! nothing =
+ createDummyTaskSeqWith 50L<µs> 300L<µs> 20
+ |> TaskSeq.tryExactlyOne
+
+ nothing |> should be None'
}
[]
-let ``TaskSeq-head gets the only item in a singleton sequence`` () = task {
- let! head = taskSeq { yield 10 } |> TaskSeq.head
- head |> should equal 10
+let ``TaskSeq-exactlyOne gets the only item in a singleton sequence`` () = task {
+ let! exactlyOne = taskSeq { yield 10 } |> TaskSeq.exactlyOne
+ exactlyOne |> should equal 10
}
[]
-let ``TaskSeq-tryHead gets the first item in a longer sequence`` () = task {
- let! head =
- createDummyTaskSeqWith 50L<µs> 1000L<µs> 50
- |> TaskSeq.tryHead
+let ``TaskSeq-tryExactlyOne gets the only item in a singleton sequence`` () = task {
+ let! exactlyOne = taskSeq { yield 10 } |> TaskSeq.tryExactlyOne
+ exactlyOne |> should be Some'
+ exactlyOne |> should equal (Some 10)
+}
+
+[]
+let ``TaskSeq-exactlyOne gets the only item in a singleton sequence - variant`` () = task {
+ let! exactlyOne =
+ createLongerDummyTaskSeq 50 300 1
+ |> TaskSeq.exactlyOne
- head |> should be Some'
- head |> should equal (Some 1)
+ exactlyOne |> should equal 1
}
[]
-let ``TaskSeq-tryHead gets the only item in a singleton sequence`` () = task {
- let! head = taskSeq { yield 10 } |> TaskSeq.tryHead
- head |> should be Some'
- head |> should equal (Some 10)
+let ``TaskSeq-tryExactlyOne gets the only item in a singleton sequence - variant`` () = task {
+ let! exactlyOne =
+ createLongerDummyTaskSeq 50 300 1
+ |> TaskSeq.tryExactlyOne
+
+ exactlyOne |> should be Some'
+ exactlyOne |> should equal (Some 1)
}
diff --git a/src/FSharpy.TaskSeq.Test/TaskSeq.Iter.Tests.fs b/src/FSharpy.TaskSeq.Test/TaskSeq.Iter.Tests.fs
index f8418b21..3738469e 100644
--- a/src/FSharpy.TaskSeq.Test/TaskSeq.Iter.Tests.fs
+++ b/src/FSharpy.TaskSeq.Test/TaskSeq.Iter.Tests.fs
@@ -2,10 +2,24 @@ module FSharpy.Tests.Iter
open Xunit
open FsUnit.Xunit
-open FsToolkit.ErrorHandling
open FSharpy
+[]
+let ``TaskSeq-iteri does nothing on empty sequences`` () = task {
+ let tq = createDummyTaskSeq 10
+ let mutable sum = -1
+ do! TaskSeq.empty |> TaskSeq.iteri (fun i _ -> sum <- sum + i)
+ sum |> should equal -1
+}
+
+[]
+let ``TaskSeq-iter does nothing on empty sequences`` () = task {
+ let tq = createDummyTaskSeq 10
+ let mutable sum = -1
+ do! TaskSeq.empty |> TaskSeq.iter (fun i -> sum <- sum + i)
+ sum |> should equal -1
+}
[]
let ``TaskSeq-iteri should go over all items`` () = task {
diff --git a/src/FSharpy.TaskSeq.Test/TaskSeq.Map.Tests.fs b/src/FSharpy.TaskSeq.Test/TaskSeq.Map.Tests.fs
index 46dc1154..08f94b4f 100644
--- a/src/FSharpy.TaskSeq.Test/TaskSeq.Map.Tests.fs
+++ b/src/FSharpy.TaskSeq.Test/TaskSeq.Map.Tests.fs
@@ -6,6 +6,11 @@ open FsToolkit.ErrorHandling
open FSharpy
+let validateSequence sequence =
+ sequence
+ |> Seq.map string
+ |> String.concat ""
+ |> should equal "ABCDEFGHIJ"
[]
let ``TaskSeq-map maps in correct order`` () = task {
@@ -14,10 +19,47 @@ let ``TaskSeq-map maps in correct order`` () = task {
|> TaskSeq.map (fun item -> char (item + 64))
|> TaskSeq.toSeqCachedAsync
- sq
- |> Seq.map string
- |> String.concat ""
- |> should equal "ABCDEFGHIJ"
+ validateSequence sq
+}
+
+[]
+let ``TaskSeq-mapi maps in correct order`` () = task {
+ let! sq =
+ createDummyTaskSeq 10
+ |> TaskSeq.mapi (fun i _ -> char (i + 65))
+ |> TaskSeq.toSeqCachedAsync
+
+ validateSequence sq
+}
+
+[]
+let ``TaskSeq-map can access mutables which are mutated in correct order`` () = task {
+ let mutable sum = 0
+
+ let! sq =
+ createDummyTaskSeq 10
+ |> TaskSeq.map (fun item ->
+ sum <- sum + 1
+ char (sum + 64))
+ |> TaskSeq.toSeqCachedAsync
+
+ sum |> should equal 10
+ validateSequence sq
+}
+
+[]
+let ``TaskSeq-mapi can access mutables which are mutated in correct order`` () = task {
+ let mutable sum = 0
+
+ let! sq =
+ createDummyTaskSeq 10
+ |> TaskSeq.mapi (fun i _ ->
+ sum <- i + 1
+ char (sum + 64))
+ |> TaskSeq.toSeqCachedAsync
+
+ sum |> should equal 10
+ validateSequence sq
}
[]
@@ -27,8 +69,48 @@ let ``TaskSeq-mapAsync maps in correct order`` () = task {
|> TaskSeq.mapAsync (fun item -> task { return char (item + 64) })
|> TaskSeq.toSeqCachedAsync
- sq
- |> Seq.map string
- |> String.concat ""
- |> should equal "ABCDEFGHIJ"
+ validateSequence sq
+}
+
+[]
+let ``TaskSeq-mapiAsync maps in correct order`` () = task {
+ let! sq =
+ createDummyTaskSeq 10
+ |> TaskSeq.mapiAsync (fun i _ -> task { return char (i + 65) })
+ |> TaskSeq.toSeqCachedAsync
+
+ validateSequence sq
+}
+
+
+[]
+let ``TaskSeq-mapAsync can access mutables which are mutated in correct order`` () = task {
+ let mutable sum = 0
+
+ let! sq =
+ createDummyTaskSeq 10
+ |> TaskSeq.mapAsync (fun item -> task {
+ sum <- sum + 1
+ return char (sum + 64)
+ })
+ |> TaskSeq.toSeqCachedAsync
+
+ sum |> should equal 10
+ validateSequence sq
+}
+
+[]
+let ``TaskSeq-mapiAsync can access mutables which are mutated in correct order`` () = task {
+ let mutable data = '0'
+
+ let! sq =
+ createDummyTaskSeq 10
+ |> TaskSeq.mapiAsync (fun i _ -> task {
+ data <- char (i + 65)
+ return data
+ })
+ |> TaskSeq.toSeqCachedAsync
+
+ data |> should equal (char 74)
+ validateSequence sq
}
diff --git a/src/FSharpy.TaskSeq.Test/TaskSeq.OfXXX.Tests.fs b/src/FSharpy.TaskSeq.Test/TaskSeq.OfXXX.Tests.fs
index 0cb51387..589f1112 100644
--- a/src/FSharpy.TaskSeq.Test/TaskSeq.OfXXX.Tests.fs
+++ b/src/FSharpy.TaskSeq.Test/TaskSeq.OfXXX.Tests.fs
@@ -47,6 +47,12 @@ let ``TaskSeq-ofTaskSeq should succeed`` () =
|> TaskSeq.ofTaskSeq
|> validateSequence
+[]
+let ``TaskSeq-ofResizeArray should succeed`` () =
+ ResizeArray [ 0..9 ]
+ |> TaskSeq.ofResizeArray
+ |> validateSequence
+
[]
let ``TaskSeq-ofArray should succeed`` () = Array.init 10 id |> TaskSeq.ofArray |> validateSequence
diff --git a/src/FSharpy.TaskSeq.Test/TaskSeq.Tests.Other.fs b/src/FSharpy.TaskSeq.Test/TaskSeq.Tests.Other.fs
index bd0100f7..09d827db 100644
--- a/src/FSharpy.TaskSeq.Test/TaskSeq.Tests.Other.fs
+++ b/src/FSharpy.TaskSeq.Test/TaskSeq.Tests.Other.fs
@@ -1,5 +1,6 @@
module FSharpy.Tests.``Other functions``
+open System.Threading.Tasks
open Xunit
open FsUnit.Xunit
open FsToolkit.ErrorHandling
@@ -14,6 +15,54 @@ let ``TaskSeq-empty returns an empty sequence`` () = task {
Seq.length sq |> should equal 0
}
+[]
+let ``TaskSeq-empty returns an empty sequence - variant`` () = task {
+ let! isEmpty = TaskSeq.empty |> TaskSeq.isEmpty
+ isEmpty |> should be True
+}
+
+[]
+let ``TaskSeq-empty in a taskSeq context`` () = task {
+ let! sq =
+ taskSeq { yield! TaskSeq.empty }
+ |> TaskSeq.toArrayAsync
+
+ Array.isEmpty sq |> should be True
+}
+
+[]
+let ``TaskSeq-empty of unit in a taskSeq context`` () = task {
+ let! sq =
+ taskSeq { yield! TaskSeq.empty }
+ |> TaskSeq.toArrayAsync
+
+ Array.isEmpty sq |> should be True
+}
+
+[]
+let ``TaskSeq-empty of more complex type in a taskSeq context`` () = task {
+ let! sq =
+ taskSeq { yield! TaskSeq.empty, int>> }
+ |> TaskSeq.toArrayAsync
+
+ Array.isEmpty sq |> should be True
+}
+
+[]
+let ``TaskSeq-empty multiple times in a taskSeq context`` () = task {
+ let! sq =
+ taskSeq {
+ yield! TaskSeq.empty
+ yield! TaskSeq.empty
+ yield! TaskSeq.empty
+ yield! TaskSeq.empty
+ yield! TaskSeq.empty
+ }
+ |> TaskSeq.toArrayAsync
+
+ Array.isEmpty sq |> should be True
+}
+
[]
let ``TaskSeq-isEmpty returns true for empty`` () = task {
let! isEmpty = TaskSeq.empty |> TaskSeq.isEmpty
@@ -25,3 +74,12 @@ let ``TaskSeq-isEmpty returns false for non-empty`` () = task {
let! isEmpty = taskSeq { yield 42 } |> TaskSeq.isEmpty
isEmpty |> should be False
}
+
+[]
+let ``TaskSeq-isEmpty returns false for delayed non-empty sequence`` () = task {
+ let! isEmpty =
+ createLongerDummyTaskSeq 200 400 3
+ |> TaskSeq.isEmpty
+
+ isEmpty |> should be False
+}
diff --git a/src/FSharpy.TaskSeq.Test/TaskSeq.Zip.Tests.fs b/src/FSharpy.TaskSeq.Test/TaskSeq.Zip.Tests.fs
new file mode 100644
index 00000000..b27c4691
--- /dev/null
+++ b/src/FSharpy.TaskSeq.Test/TaskSeq.Zip.Tests.fs
@@ -0,0 +1,112 @@
+module FSharpy.Tests.Zip
+
+open System
+open Xunit
+open FsUnit.Xunit
+open FsToolkit.ErrorHandling
+
+open FSharpy
+
+[]
+let ``TaskSeq-zip zips in correct order`` () = task {
+ let one = createDummyTaskSeq 10
+ let two = createDummyTaskSeq 10
+ let combined = TaskSeq.zip one two
+ let! combined = TaskSeq.toArrayAsync combined
+
+ combined
+ |> Array.forall (fun (x, y) -> x = y)
+ |> should be True
+
+ combined |> should be (haveLength 10)
+
+ combined
+ |> should equal (Array.init 10 (fun x -> x + 1, x + 1))
+}
+
+[]
+let ``TaskSeq-zip zips in correct order for differently delayed sequences`` () = task {
+ let one = createDummyDirectTaskSeq 10
+ let two = createDummyTaskSeq 10
+ let combined = TaskSeq.zip one two
+ let! combined = TaskSeq.toArrayAsync combined
+
+ combined
+ |> Array.forall (fun (x, y) -> x = y)
+ |> should be True
+
+ combined |> should be (haveLength 10)
+
+ combined
+ |> should equal (Array.init 10 (fun x -> x + 1, x + 1))
+}
+
+[]
+let ``TaskSeq-zip zips large sequences just fine`` length = task {
+ let one = createDummyTaskSeqWith 10L<µs> 50L<µs> length
+ let two = createDummyDirectTaskSeq length
+ let combined = TaskSeq.zip one two
+ let! combined = TaskSeq.toArrayAsync combined
+
+ combined
+ |> Array.forall (fun (x, y) -> x = y)
+ |> should be True
+
+ combined |> should be (haveLength length)
+ combined |> Array.last |> should equal (length, length)
+}
+
+[]
+let ``TaskSeq-zip zips different types`` () = task {
+ let one = taskSeq {
+ yield "one"
+ yield "two"
+ }
+
+ let two = taskSeq {
+ yield 42L
+ yield 43L
+ }
+
+ let combined = TaskSeq.zip one two
+ let! combined = TaskSeq.toArrayAsync combined
+
+ combined |> should equal [| ("one", 42L); ("two", 43L) |]
+}
+
+[]
+let ``TaskSeq-zip throws on unequal lengths, variant`` leftThrows = task {
+ let long = createDummyTaskSeq 11
+ let short = createDummyTaskSeq 10
+
+ let combined =
+ if leftThrows then
+ TaskSeq.zip short long
+ else
+ TaskSeq.zip long short
+
+ fun () -> TaskSeq.toArrayAsync combined |> Task.ignore
+ |> should throwAsyncExact typeof
+}
+
+[]
+let ``TaskSeq-zip throws on unequal lengths with empty seq`` leftThrows = task {
+ let one = createDummyTaskSeq 1
+
+ let combined =
+ if leftThrows then
+ TaskSeq.zip TaskSeq.empty one
+ else
+ TaskSeq.zip one TaskSeq.empty
+
+ fun () -> TaskSeq.toArrayAsync combined |> Task.ignore
+ |> should throwAsyncExact typeof
+}
+
+[]
+let ``TaskSeq-zip can zip empty arrays`` () = task {
+ let combined = TaskSeq.zip TaskSeq.empty TaskSeq.empty
+ let! combined = TaskSeq.toArrayAsync combined
+ combined |> should be Empty
+ Array.isEmpty combined |> should be True
+}
diff --git a/src/FSharpy.TaskSeq.Test/TestUtils.fs b/src/FSharpy.TaskSeq.Test/TestUtils.fs
index 10a46e01..e3e3b85d 100644
--- a/src/FSharpy.TaskSeq.Test/TestUtils.fs
+++ b/src/FSharpy.TaskSeq.Test/TestUtils.fs
@@ -29,7 +29,7 @@ module DelayHelper =
/// True to allow yielding the thread. If this is set to false, on single-proc systems
/// this will prevent all other code from running.
///
- let delayMicroseconds microseconds (allowThreadYield: bool) =
+ let spinWaitDelay (microseconds: int64<µs>) (allowThreadYield: bool) =
let start = Stopwatch.GetTimestamp()
let minimumTicks = int64 microseconds * Stopwatch.Frequency / 1_000_000L
@@ -48,7 +48,7 @@ module DelayHelper =
///
-/// Creates dummy tasks with a randomized delay and a mutable state,
+/// Creates dummy backgroundTasks with a randomized delay and a mutable state,
/// to ensure we properly test whether processing is done ordered or not.
/// Default for and
/// are 10,000µs and 30,000µs respectively (or 10ms and 30ms).
@@ -63,10 +63,20 @@ type DummyTaskFactory(µsecMin: int64<µs>, µsecMax: int64<µs>) =
// DO NOT use Thead.Sleep(), it's blocking!
// WARNING: Task.Delay only has a 15ms timer resolution!!!
//let! _ = Task.Delay(rnd ())
- let! _ = Task.Delay 0 // this creates a resume state, which seems more efficient than SpinWait.SpinOnce, see DelayHelper.
- DelayHelper.delayMicroseconds (rnd ()) false
+
+ // TODO: check this! The following comment may not be correct
+ // this creates a resume state, which seems more efficient than SpinWait.SpinOnce, see DelayHelper.
+ let! _ = Task.Delay 0
+ let delay = rnd ()
+
+ // typical minimum accuracy of Task.Delay is 15.6ms
+ // for delay-cases shorter than that, we use SpinWait
+ if delay < 15_000L<µs> then
+ do DelayHelper.spinWaitDelay (rnd ()) false
+ else
+ do! Task.Delay(int <| float delay / 1_000.0)
+
Interlocked.Increment &x |> ignore
- //x <- x + 1
return x // this dereferences the variable
}
@@ -150,6 +160,18 @@ module TestUtils =
yield x
}
+ /// Create a bunch of dummy tasks, with varying millisecond delays.
+ let createLongerDummyTaskSeq (min: int) max count =
+ /// Set of delayed tasks in the form of `unit -> Task`
+ let tasks = DummyTaskFactory(min, max).CreateDelayedTasks count
+
+ taskSeq {
+ for task in tasks do
+ // cannot use `yield!` here, as `taskSeq` expects it to return a seq
+ let! x = task ()
+ yield x
+ }
+
/// Create a bunch of dummy tasks, which are sequentially hot-started, WITHOUT artificial spin-wait delays.
let createDummyDirectTaskSeq count =
/// Set of delayed tasks in the form of `unit -> Task`
diff --git a/src/FSharpy.TaskSeq/TaskSeq.fs b/src/FSharpy.TaskSeq/TaskSeq.fs
index b8157f6b..c05334f9 100644
--- a/src/FSharpy.TaskSeq/TaskSeq.fs
+++ b/src/FSharpy.TaskSeq/TaskSeq.fs
@@ -53,6 +53,7 @@ module TaskSeq =
e.DisposeAsync().AsTask().Wait()
}
+ // FIXME: incomplete and incorrect code!!!
let toSeqOfTasks (taskSeq: taskSeq<'T>) = seq {
let e = taskSeq.GetAsyncEnumerator(CancellationToken())
diff --git a/src/FSharpy.TaskSeq/TaskSeqInternal.fs b/src/FSharpy.TaskSeq/TaskSeqInternal.fs
index 97d350a8..8b39781d 100644
--- a/src/FSharpy.TaskSeq/TaskSeqInternal.fs
+++ b/src/FSharpy.TaskSeq/TaskSeqInternal.fs
@@ -173,24 +173,29 @@ module internal TaskSeqInternal =
}
let zip (taskSequence1: taskSeq<_>) (taskSequence2: taskSeq<_>) = taskSeq {
+ let inline validate step1 step2 =
+ if step1 <> step2 then
+ if step1 then
+ invalidArg "taskSequence1" "The task sequences have different lengths."
+
+ if step2 then
+ invalidArg "taskSequence2" "The task sequences have different lengths."
+
+
let e1 = taskSequence1.GetAsyncEnumerator(CancellationToken())
let e2 = taskSequence2.GetAsyncEnumerator(CancellationToken())
let mutable go = true
let! step1 = e1.MoveNextAsync()
- let! step2 = e1.MoveNextAsync()
+ let! step2 = e2.MoveNextAsync()
go <- step1 && step2
+ validate step1 step2
while go do
yield e1.Current, e2.Current
let! step1 = e1.MoveNextAsync()
- let! step2 = e1.MoveNextAsync()
+ let! step2 = e2.MoveNextAsync()
+ validate step1 step2
go <- step1 && step2
-
- if step1 then
- invalidArg "taskSequence1" "The task sequences have different lengths."
-
- if step2 then
- invalidArg "taskSequence2" "The task sequences have different lengths."
}
let collect (binder: _ -> #IAsyncEnumerable<_>) (taskSequence: taskSeq<_>) = taskSeq {