Skip to content

Commit 13d3e42

Browse files
authored
Merge pull request #308 from fsprojects/repo-assist/improve-oflist-ofarray-cycle-20260413-aff071fc4272ec05
[Repo Assist] Add AsyncSeq.ofList, AsyncSeq.ofArray, and AsyncSeq.cycle
2 parents d58ab85 + b949bbd commit 13d3e42

File tree

4 files changed

+123
-0
lines changed

4 files changed

+123
-0
lines changed

RELEASE_NOTES.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
### 4.13.0
2+
3+
* Added `AsyncSeq.ofList` — creates an async sequence from an F# list with an optimised direct-enumerator implementation (avoids `IEnumerator<T>` boxing).
4+
* Added `AsyncSeq.ofArray` — creates an async sequence from an array with an optimised index-based enumerator (avoids `IEnumerator<T>` boxing).
5+
* Added `AsyncSeq.cycle` — infinitely cycles through all elements of a source async sequence; returns empty if the source is empty.
6+
17
### 4.12.0
28

39
* Tests: Added tests for `mapiAsync`, `tryPickAsync`, `pickAsync`, and `groupByAsync` — these four async functions previously had no test coverage.

src/FSharp.Control.AsyncSeq/AsyncSeq.fs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -790,6 +790,35 @@ module AsyncSeq =
790790
dispose e
791791
| _ -> () }) :> AsyncSeq<'T>
792792

793+
let ofList (source: 'T list) : AsyncSeq<'T> =
794+
AsyncSeqImpl(fun () ->
795+
let mutable remaining = source
796+
{ new IAsyncSeqEnumerator<'T> with
797+
member _.MoveNext() =
798+
async {
799+
match remaining with
800+
| [] -> return None
801+
| h :: t ->
802+
remaining <- t
803+
return Some h
804+
}
805+
member _.Dispose() = () }) :> AsyncSeq<'T>
806+
807+
let ofArray (source: 'T []) : AsyncSeq<'T> =
808+
AsyncSeqImpl(fun () ->
809+
let mutable i = 0
810+
{ new IAsyncSeqEnumerator<'T> with
811+
member _.MoveNext() =
812+
async {
813+
if i < source.Length then
814+
let v = source.[i]
815+
i <- i + 1
816+
return Some v
817+
else
818+
return None
819+
}
820+
member _.Dispose() = () }) :> AsyncSeq<'T>
821+
793822
let appendSeq (seq2: seq<'T>) (source: AsyncSeq<'T>) : AsyncSeq<'T> =
794823
append source (ofSeq seq2)
795824

@@ -2160,6 +2189,15 @@ module AsyncSeq =
21602189
let toArraySynchronously (source:AsyncSeq<'T>) = toArrayAsync source |> Async.RunSynchronously
21612190
#endif
21622191

2192+
let cycle (source: AsyncSeq<'T>) : AsyncSeq<'T> =
2193+
asyncSeq {
2194+
let! arr = source |> toArrayAsync
2195+
if arr.Length > 0 then
2196+
while true do
2197+
for x in arr do
2198+
yield x
2199+
}
2200+
21632201
let partitionAsync (predicate: 'T -> Async<bool>) (source: AsyncSeq<'T>) : Async<'T[] * 'T[]> = async {
21642202
let trues = ResizeArray<'T>()
21652203
let falses = ResizeArray<'T>()

src/FSharp.Control.AsyncSeq/AsyncSeq.fsi

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@ module AsyncSeq =
6060
/// Creates an async sequence given by evaluating the specified async computation until it returns None.
6161
val replicateUntilNoneAsync : Async<'T option> -> AsyncSeq<'T>
6262

63+
/// Returns an async sequence which infinitely cycles through all elements of the source sequence.
64+
/// The source is materialised into an array on first enumeration. Returns an empty sequence if
65+
/// the source is empty.
66+
val cycle : source:AsyncSeq<'T> -> AsyncSeq<'T>
67+
6368
/// Returns an async sequence which emits an element on a specified period.
6469
val intervalMs : periodMs:int -> AsyncSeq<DateTime>
6570

@@ -488,6 +493,14 @@ module AsyncSeq =
488493
/// input synchronous sequence and returns them one-by-one.
489494
val ofSeq : source:seq<'T> -> AsyncSeq<'T>
490495

496+
/// Creates an asynchronous sequence that lazily takes elements from an
497+
/// F# list and returns them one-by-one.
498+
val ofList : source:'T list -> AsyncSeq<'T>
499+
500+
/// Creates an asynchronous sequence that lazily takes elements from an
501+
/// array and returns them one-by-one.
502+
val ofArray : source:'T [] -> AsyncSeq<'T>
503+
491504
/// Creates an asynchronous sequence that lazily takes element from an
492505
/// input synchronous sequence of asynchronous computation and returns them one-by-one.
493506
val ofSeqAsync : seq<Async<'T>> -> AsyncSeq<'T>

tests/FSharp.Control.AsyncSeq.Tests/AsyncSeqTests.fs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4436,3 +4436,69 @@ let ``AsyncSeq.groupByAsync with all-same key produces single group`` () =
44364436
|> AsyncSeq.toArrayAsync
44374437
|> Async.RunSynchronously
44384438
Assert.AreEqual([| ("same", [|1;2;3|]) |], result)
4439+
4440+
// ===== ofList =====
4441+
4442+
[<Test>]
4443+
let ``AsyncSeq.ofList returns elements in order`` () =
4444+
let result = AsyncSeq.ofList [1; 2; 3; 4; 5] |> AsyncSeq.toArrayAsync |> Async.RunSynchronously
4445+
Assert.AreEqual([| 1; 2; 3; 4; 5 |], result)
4446+
4447+
[<Test>]
4448+
let ``AsyncSeq.ofList on empty list returns empty`` () =
4449+
let result = AsyncSeq.ofList ([] : int list) |> AsyncSeq.toArrayAsync |> Async.RunSynchronously
4450+
Assert.AreEqual([||], result)
4451+
4452+
[<Test>]
4453+
let ``AsyncSeq.ofList produces same result as ofSeq`` () =
4454+
let xs = [10; 20; 30]
4455+
let fromList = AsyncSeq.ofList xs |> AsyncSeq.toArrayAsync |> Async.RunSynchronously
4456+
let fromSeq = AsyncSeq.ofSeq xs |> AsyncSeq.toArrayAsync |> Async.RunSynchronously
4457+
Assert.AreEqual(fromSeq, fromList)
4458+
4459+
// ===== ofArray =====
4460+
4461+
[<Test>]
4462+
let ``AsyncSeq.ofArray returns elements in order`` () =
4463+
let result = AsyncSeq.ofArray [| 10; 20; 30 |] |> AsyncSeq.toArrayAsync |> Async.RunSynchronously
4464+
Assert.AreEqual([| 10; 20; 30 |], result)
4465+
4466+
[<Test>]
4467+
let ``AsyncSeq.ofArray on empty array returns empty`` () =
4468+
let result = AsyncSeq.ofArray ([||] : int []) |> AsyncSeq.toArrayAsync |> Async.RunSynchronously
4469+
Assert.AreEqual([||], result)
4470+
4471+
[<Test>]
4472+
let ``AsyncSeq.ofArray produces same result as ofSeq`` () =
4473+
let xs = [| 1; 2; 3; 4 |]
4474+
let fromArray = AsyncSeq.ofArray xs |> AsyncSeq.toArrayAsync |> Async.RunSynchronously
4475+
let fromSeq = AsyncSeq.ofSeq xs |> AsyncSeq.toArrayAsync |> Async.RunSynchronously
4476+
Assert.AreEqual(fromSeq, fromArray)
4477+
4478+
// ===== cycle =====
4479+
4480+
[<Test>]
4481+
let ``AsyncSeq.cycle repeats elements indefinitely`` () =
4482+
let result =
4483+
AsyncSeq.cycle (AsyncSeq.ofList [1; 2; 3])
4484+
|> AsyncSeq.take 7
4485+
|> AsyncSeq.toArrayAsync
4486+
|> Async.RunSynchronously
4487+
Assert.AreEqual([| 1; 2; 3; 1; 2; 3; 1 |], result)
4488+
4489+
[<Test>]
4490+
let ``AsyncSeq.cycle on empty sequence returns empty`` () =
4491+
let result =
4492+
AsyncSeq.cycle AsyncSeq.empty<int>
4493+
|> AsyncSeq.toArrayAsync
4494+
|> Async.RunSynchronously
4495+
Assert.AreEqual([||], result)
4496+
4497+
[<Test>]
4498+
let ``AsyncSeq.cycle on singleton repeats single element`` () =
4499+
let result =
4500+
AsyncSeq.cycle (AsyncSeq.singleton 42)
4501+
|> AsyncSeq.take 5
4502+
|> AsyncSeq.toArrayAsync
4503+
|> Async.RunSynchronously
4504+
Assert.AreEqual([| 42; 42; 42; 42; 42 |], result)

0 commit comments

Comments
 (0)