From b6ef08fc52b0de584ea0d74d83271b4f08b9a189 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 9 Mar 2026 00:10:11 +0000 Subject: [PATCH 1/2] =?UTF-8?q?Add=20AsyncSeq.transpose=20=E2=80=94=20mirr?= =?UTF-8?q?or=20Seq.transpose=20for=20async=20sequences?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Transposes rows and columns of an AsyncSeq>, yielding each column as a 'T array. Buffers all rows before yielding columns. Raises InvalidOperationException if inner sequences have different lengths. - Signature in AsyncSeq.fsi - 6 tests covering basic transpose, empty, single row, single column, singleton, and jagged-input error case - RELEASE_NOTES.md: 4.9.0 entry 323/323 tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- RELEASE_NOTES.md | 4 ++ src/FSharp.Control.AsyncSeq/AsyncSeq.fs | 12 ++++ src/FSharp.Control.AsyncSeq/AsyncSeq.fsi | 6 ++ .../AsyncSeqTests.fs | 66 +++++++++++++++++++ 4 files changed, 88 insertions(+) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index c88d2e5..1956545 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,3 +1,7 @@ +### 4.9.0 + +* Added `AsyncSeq.transpose` — transposes an async sequence of sequences, yielding each column as an array; buffers all rows before yielding; mirrors `Seq.transpose`. Raises `InvalidOperationException` if inner sequences have different lengths. + ### 4.8.0 * Added `AsyncSeq.mapFoldAsync` — maps each element using an asynchronous folder that also threads an accumulator state, returning both the array of results and the final state; mirrors `Seq.mapFold`. diff --git a/src/FSharp.Control.AsyncSeq/AsyncSeq.fs b/src/FSharp.Control.AsyncSeq/AsyncSeq.fs index bf536f4..0a502bf 100644 --- a/src/FSharp.Control.AsyncSeq/AsyncSeq.fs +++ b/src/FSharp.Control.AsyncSeq/AsyncSeq.fs @@ -2035,6 +2035,18 @@ module AsyncSeq = let! arr = toArrayAsync source for i in arr.Length - 1 .. -1 .. 0 do yield arr.[i] } + + /// Transposes the rows and columns of an async sequence of sequences. + /// Buffers the entire source sequence. Raises InvalidOperationException if inner sequences + /// have different lengths. Mirrors Seq.transpose. + let transpose (source: AsyncSeq>) : AsyncSeq<'T[]> = asyncSeq { + let! rows = toListAsync (source |> map Seq.toArray) + if not rows.IsEmpty then + let firstLen = rows.Head.Length + if rows |> List.exists (fun row -> row.Length <> firstLen) then + invalidOp "The input sequences have different lengths." + for col in 0 .. firstLen - 1 do + yield rows |> List.map (fun row -> row.[col]) |> List.toArray } #endif #if !FABLE_COMPILER diff --git a/src/FSharp.Control.AsyncSeq/AsyncSeq.fsi b/src/FSharp.Control.AsyncSeq/AsyncSeq.fsi index 5113dd6..6e23a09 100644 --- a/src/FSharp.Control.AsyncSeq/AsyncSeq.fsi +++ b/src/FSharp.Control.AsyncSeq/AsyncSeq.fsi @@ -673,6 +673,12 @@ module AsyncSeq = /// sequence is buffered before yielding any elements, mirroring Seq.rev. /// This function should not be used with large or infinite sequences. val rev : source:AsyncSeq<'T> -> AsyncSeq<'T> + + /// Transposes the rows and columns of an async sequence of sequences, yielding each + /// column as an array. The entire source sequence is buffered before any column is yielded, + /// mirroring Seq.transpose. Raises InvalidOperationException if inner sequences have + /// different lengths. This function should not be used with large or infinite sequences. + val transpose : source:AsyncSeq> -> AsyncSeq<'T[]> #endif /// Interleaves two async sequences of the same type into a resulting sequence. The provided diff --git a/tests/FSharp.Control.AsyncSeq.Tests/AsyncSeqTests.fs b/tests/FSharp.Control.AsyncSeq.Tests/AsyncSeqTests.fs index 17277e6..fc1a0a0 100644 --- a/tests/FSharp.Control.AsyncSeq.Tests/AsyncSeqTests.fs +++ b/tests/FSharp.Control.AsyncSeq.Tests/AsyncSeqTests.fs @@ -3662,3 +3662,69 @@ let ``AsyncSeq.insertAt raises ArgumentException when index exceeds length`` () |> AsyncSeq.toArrayAsync |> Async.RunSynchronously |> ignore) |> ignore + +// ===== transpose ===== + +[] +let ``AsyncSeq.transpose basic 2x3 matrix`` () = + let source = + asyncSeq { + yield seq { yield 1; yield 2; yield 3 } + yield seq { yield 4; yield 5; yield 6 } + } + let result = AsyncSeq.transpose source |> AsyncSeq.toArrayAsync |> Async.RunSynchronously + Assert.AreEqual(3, result.Length) + Assert.AreEqual([| 1; 4 |], result.[0]) + Assert.AreEqual([| 2; 5 |], result.[1]) + Assert.AreEqual([| 3; 6 |], result.[2]) + +[] +let ``AsyncSeq.transpose empty outer sequence yields empty`` () = + let result = + AsyncSeq.empty> + |> AsyncSeq.transpose + |> AsyncSeq.toArrayAsync + |> Async.RunSynchronously + Assert.AreEqual([||], result) + +[] +let ``AsyncSeq.transpose single row returns one column per element`` () = + let source = asyncSeq { yield seq { yield 1; yield 2; yield 3 } } + let result = AsyncSeq.transpose source |> AsyncSeq.toArrayAsync |> Async.RunSynchronously + Assert.AreEqual(3, result.Length) + Assert.AreEqual([| 1 |], result.[0]) + Assert.AreEqual([| 2 |], result.[1]) + Assert.AreEqual([| 3 |], result.[2]) + +[] +let ``AsyncSeq.transpose single column returns one row per element`` () = + let source = + asyncSeq { + yield seq { yield 1 } + yield seq { yield 2 } + yield seq { yield 3 } + } + let result = AsyncSeq.transpose source |> AsyncSeq.toArrayAsync |> Async.RunSynchronously + Assert.AreEqual(1, result.Length) + Assert.AreEqual([| 1; 2; 3 |], result.[0]) + +[] +let ``AsyncSeq.transpose of singleton rows yields one column`` () = + let source = asyncSeq { yield seq { yield 7 }; yield seq { yield 8 } } + let result = AsyncSeq.transpose source |> AsyncSeq.toArrayAsync |> Async.RunSynchronously + Assert.AreEqual(1, result.Length) + Assert.AreEqual([| 7; 8 |], result.[0]) + +[] +let ``AsyncSeq.transpose raises InvalidOperationException for jagged input`` () = + let source = + asyncSeq { + yield seq { yield 1; yield 2 } + yield seq { yield 3 } + } + Assert.Throws(fun () -> + AsyncSeq.transpose source + |> AsyncSeq.toArrayAsync + |> Async.RunSynchronously + |> ignore) + |> ignore From 1c33c4e7847b96f41832046f9b01b880d6cc5c3a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 9 Mar 2026 00:14:45 +0000 Subject: [PATCH 2/2] ci: trigger checks