From 9ff720670b922f1fb0725e8c5c7525db601b4546 Mon Sep 17 00:00:00 2001 From: Repo Assist Date: Wed, 25 Feb 2026 17:23:27 +0000 Subject: [PATCH 1/2] Add AsyncSeq.zip3, zipWith3, zipWithAsync3 Three new combinators mirroring Seq.zip3 for combining three async sequences: - zip3: combines three async sequences into a sequence of triples - zipWith3: applies a 3-argument function across three async sequences - zipWithAsync3: async variant of zipWith3 3 new tests added; all 237 tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/FSharp.Control.AsyncSeq/AsyncSeq.fs | 26 ++++++++++++++ src/FSharp.Control.AsyncSeq/AsyncSeq.fsi | 12 +++++++ .../AsyncSeqTests.fs | 36 +++++++++++++++++++ 3 files changed, 74 insertions(+) diff --git a/src/FSharp.Control.AsyncSeq/AsyncSeq.fs b/src/FSharp.Control.AsyncSeq/AsyncSeq.fs index c24253b..1e3f134 100644 --- a/src/FSharp.Control.AsyncSeq/AsyncSeq.fs +++ b/src/FSharp.Control.AsyncSeq/AsyncSeq.fs @@ -1552,6 +1552,32 @@ module AsyncSeq = let zipWithIndexAsync (f:int64 -> 'T -> Async<'U>) (s:AsyncSeq<'T>) : AsyncSeq<'U> = mapiAsync f s + let zipWithAsync3 (f:'T1 -> 'T2 -> 'T3 -> Async<'U>) (source1:AsyncSeq<'T1>) (source2:AsyncSeq<'T2>) (source3:AsyncSeq<'T3>) : AsyncSeq<'U> = asyncSeq { + use ie1 = source1.GetEnumerator() + use ie2 = source2.GetEnumerator() + use ie3 = source3.GetEnumerator() + let! move1 = ie1.MoveNext() + let! move2 = ie2.MoveNext() + let! move3 = ie3.MoveNext() + let b1 = ref move1 + let b2 = ref move2 + let b3 = ref move3 + while b1.Value.IsSome && b2.Value.IsSome && b3.Value.IsSome do + let! res = f b1.Value.Value b2.Value.Value b3.Value.Value + yield res + let! move1n = ie1.MoveNext() + let! move2n = ie2.MoveNext() + let! move3n = ie3.MoveNext() + b1 := move1n + b2 := move2n + b3 := move3n } + + let zip3 (source1:AsyncSeq<'T1>) (source2:AsyncSeq<'T2>) (source3:AsyncSeq<'T3>) : AsyncSeq<'T1 * 'T2 * 'T3> = + zipWithAsync3 (fun a b c -> async.Return (a, b, c)) source1 source2 source3 + + let zipWith3 (f:'T1 -> 'T2 -> 'T3 -> 'U) (source1:AsyncSeq<'T1>) (source2:AsyncSeq<'T2>) (source3:AsyncSeq<'T3>) : AsyncSeq<'U> = + zipWithAsync3 (fun a b c -> f a b c |> async.Return) source1 source2 source3 + let zappAsync (fs:AsyncSeq<'T -> Async<'U>>) (s:AsyncSeq<'T>) : AsyncSeq<'U> = zipWithAsync (|>) s fs diff --git a/src/FSharp.Control.AsyncSeq/AsyncSeq.fsi b/src/FSharp.Control.AsyncSeq/AsyncSeq.fsi index 2bc95dc..a48e762 100644 --- a/src/FSharp.Control.AsyncSeq/AsyncSeq.fsi +++ b/src/FSharp.Control.AsyncSeq/AsyncSeq.fsi @@ -406,6 +406,18 @@ module AsyncSeq = /// The resulting sequence stops when either of the argument sequences stop. val zipWithParallel : mapping:('T1 -> 'T2 -> 'U) -> source1:AsyncSeq<'T1> -> source2:AsyncSeq<'T2> -> AsyncSeq<'U> + /// Combines three asynchronous sequences using the specified asynchronous function. + /// The resulting sequence stops when any of the argument sequences stop. + val zipWithAsync3 : mapping:('T1 -> 'T2 -> 'T3 -> Async<'U>) -> source1:AsyncSeq<'T1> -> source2:AsyncSeq<'T2> -> source3:AsyncSeq<'T3> -> AsyncSeq<'U> + + /// Combines three asynchronous sequences into a sequence of triples. + /// The resulting sequence stops when any of the argument sequences stop. + val zip3 : source1:AsyncSeq<'T1> -> source2:AsyncSeq<'T2> -> source3:AsyncSeq<'T3> -> AsyncSeq<'T1 * 'T2 * 'T3> + + /// Combines three asynchronous sequences using the specified function. + /// The resulting sequence stops when any of the argument sequences stop. + val zipWith3 : mapping:('T1 -> 'T2 -> 'T3 -> 'U) -> source1:AsyncSeq<'T1> -> source2:AsyncSeq<'T2> -> source3:AsyncSeq<'T3> -> AsyncSeq<'U> + /// Builds a new asynchronous sequence whose elements are generated by /// applying the specified function to all elements of the input sequence. /// diff --git a/tests/FSharp.Control.AsyncSeq.Tests/AsyncSeqTests.fs b/tests/FSharp.Control.AsyncSeq.Tests/AsyncSeqTests.fs index 597ea33..e540f38 100644 --- a/tests/FSharp.Control.AsyncSeq.Tests/AsyncSeqTests.fs +++ b/tests/FSharp.Control.AsyncSeq.Tests/AsyncSeqTests.fs @@ -895,6 +895,42 @@ let ``AsyncSeq.zipWithAsyncParallel``() = let expected = Seq.zip la lb |> Seq.map ((<||) (+)) |> AsyncSeq.ofSeq Assert.True(EQ expected actual) +[] +let ``AsyncSeq.zip3``() = + for la in [ []; [1]; [1;2;3;4;5] ] do + for lb in [ []; [1]; [1;2;3;4;5] ] do + for lc in [ []; [1]; [1;2;3;4;5] ] do + let a = la |> AsyncSeq.ofSeq + let b = lb |> AsyncSeq.ofSeq + let c = lc |> AsyncSeq.ofSeq + let actual = AsyncSeq.zip3 a b c + let expected = Seq.zip3 la lb lc |> AsyncSeq.ofSeq + Assert.True(EQ expected actual) + +[] +let ``AsyncSeq.zipWith3``() = + for la in [ []; [1]; [1;2;3;4;5] ] do + for lb in [ []; [1]; [1;2;3;4;5] ] do + for lc in [ []; [1]; [1;2;3;4;5] ] do + let a = la |> AsyncSeq.ofSeq + let b = lb |> AsyncSeq.ofSeq + let c = lc |> AsyncSeq.ofSeq + let actual = AsyncSeq.zipWith3 (fun a b c -> a + b + c) a b c + let expected = Seq.zip3 la lb lc |> Seq.map (fun (a,b,c) -> a+b+c) |> AsyncSeq.ofSeq + Assert.True(EQ expected actual) + +[] +let ``AsyncSeq.zipWithAsync3``() = + for la in [ []; [1]; [1;2;3;4;5] ] do + for lb in [ []; [1]; [1;2;3;4;5] ] do + for lc in [ []; [1]; [1;2;3;4;5] ] do + let a = la |> AsyncSeq.ofSeq + let b = lb |> AsyncSeq.ofSeq + let c = lc |> AsyncSeq.ofSeq + let actual = AsyncSeq.zipWithAsync3 (fun a b c -> a + b + c |> async.Return) a b c + let expected = Seq.zip3 la lb lc |> Seq.map (fun (a,b,c) -> a+b+c) |> AsyncSeq.ofSeq + Assert.True(EQ expected actual) + [] let ``AsyncSeq.append works``() = for la in [ []; [1]; [1;2;3;4;5] ] do From f2260fd7296ca57c8483b06c820b0b8edb5a6fed Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 25 Feb 2026 17:27:09 +0000 Subject: [PATCH 2/2] ci: trigger CI checks