Skip to content

Commit 2c270e6

Browse files
authored
Merge branch 'main' into repo-assist/improve-new-combinators-87b33ebcb712bf81
2 parents 3390dca + ced9d2e commit 2c270e6

8 files changed

Lines changed: 583 additions & 150 deletions

File tree

.github/aw/actions-lock.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@
1010
"version": "v8",
1111
"sha": "ed597411d8f924073f98dfc5c65a23a2325f34cd"
1212
},
13-
"github/gh-aw/actions/setup@v0.52.1": {
13+
"github/gh-aw/actions/setup@v0.53.6": {
1414
"repo": "github/gh-aw/actions/setup",
15-
"version": "v0.52.1",
16-
"sha": "a86e657586e4ac5f549a790628971ec02f6a4a8f"
15+
"version": "v0.53.6",
16+
"sha": "956f874e40e831c08a8b01ec76f5d49ae3fe8387"
1717
}
1818
}
1919
}

.github/workflows/repo-assist.lock.yml

Lines changed: 185 additions & 78 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.github/workflows/repo-assist.md

Lines changed: 141 additions & 68 deletions
Large diffs are not rendered by default.

RELEASE_NOTES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
* Added `AsyncSeq.mapFold` — synchronous variant of `AsyncSeq.mapFoldAsync`, mirroring `Seq.mapFold`.
55
* Added `AsyncSeq.allPairs` — returns an async sequence of all pairs from two input sequences (cartesian product); the second source is fully buffered before iteration, mirroring `Seq.allPairs`.
66
* Added `AsyncSeq.rev` — returns a new async sequence with all elements in reverse order; the entire source sequence is buffered before yielding, mirroring `Seq.rev`.
7+
* Added `AsyncSeq.splitAt` — splits a sequence at the given index, returning the first `count` elements as an array and the remaining elements as a new `AsyncSeq`. Mirrors `Seq.splitAt`. The source is enumerated once.
8+
* Added `AsyncSeq.removeAt` — returns a new sequence with the element at the specified index removed, mirroring `Seq.removeAt`.
9+
* Added `AsyncSeq.updateAt` — returns a new sequence with the element at the specified index replaced by a given value, mirroring `Seq.updateAt`.
10+
* Added `AsyncSeq.insertAt` — returns a new sequence with a value inserted before the element at the specified index (or appended if the index equals the sequence length), mirroring `Seq.insertAt`.
711

812
### 4.6.0
913

src/FSharp.Control.AsyncSeq/AsyncSeq.fs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1449,6 +1449,32 @@ module AsyncSeq =
14491449
let s = System.Collections.Generic.HashSet(excluded)
14501450
source |> filter (fun x -> not (s.Contains(x)))
14511451

1452+
let removeAt (index : int) (source : AsyncSeq<'T>) : AsyncSeq<'T> = asyncSeq {
1453+
if index < 0 then invalidArg "index" "must be non-negative"
1454+
let i = ref 0
1455+
for x in source do
1456+
if i.Value <> index then yield x
1457+
i := i.Value + 1 }
1458+
1459+
let updateAt (index : int) (value : 'T) (source : AsyncSeq<'T>) : AsyncSeq<'T> = asyncSeq {
1460+
if index < 0 then invalidArg "index" "must be non-negative"
1461+
let i = ref 0
1462+
for x in source do
1463+
if i.Value = index then yield value
1464+
else yield x
1465+
i := i.Value + 1 }
1466+
1467+
let insertAt (index : int) (value : 'T) (source : AsyncSeq<'T>) : AsyncSeq<'T> = asyncSeq {
1468+
if index < 0 then invalidArg "index" "must be non-negative"
1469+
let i = ref 0
1470+
for x in source do
1471+
if i.Value = index then yield value
1472+
yield x
1473+
i := i.Value + 1
1474+
if i.Value = index then yield value
1475+
elif i.Value < index then
1476+
invalidArg "index" "The index is outside the range of elements in the collection." }
1477+
14521478
#if !FABLE_COMPILER
14531479
let iterAsyncParallel (f:'a -> Async<unit>) (s:AsyncSeq<'a>) : Async<unit> = async {
14541480
use mb = MailboxProcessor.Start (ignore >> async.Return)
@@ -1851,6 +1877,35 @@ module AsyncSeq =
18511877

18521878
let tail (source : AsyncSeq<'T>) : AsyncSeq<'T> = skip 1 source
18531879

1880+
/// Splits an async sequence at the given index, returning the first `count` elements as an array
1881+
/// and the remaining elements as a new AsyncSeq. The source is enumerated once.
1882+
let splitAt (count: int) (source: AsyncSeq<'T>) : Async<'T array * AsyncSeq<'T>> = async {
1883+
if count < 0 then invalidArg "count" "must be non-negative"
1884+
let ie = source.GetEnumerator()
1885+
let ra = ResizeArray<'T>()
1886+
let! m = ie.MoveNext()
1887+
let b = ref m
1888+
while b.Value.IsSome && ra.Count < count do
1889+
ra.Add b.Value.Value
1890+
let! next = ie.MoveNext()
1891+
b := next
1892+
let first = ra.ToArray()
1893+
let rest =
1894+
if b.Value.IsNone then
1895+
ie.Dispose()
1896+
empty<'T>
1897+
else
1898+
let cur = ref b.Value
1899+
asyncSeq {
1900+
try
1901+
while cur.Value.IsSome do
1902+
yield cur.Value.Value
1903+
let! next = ie.MoveNext()
1904+
cur := next
1905+
finally
1906+
ie.Dispose() }
1907+
return first, rest }
1908+
18541909
let toArrayAsync (source : AsyncSeq<'T>) : Async<'T[]> = async {
18551910
let ra = (new ResizeArray<_>())
18561911
use ie = source.GetEnumerator()

src/FSharp.Control.AsyncSeq/AsyncSeq.fsi

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,19 @@ module AsyncSeq =
418418
/// in the given excluded collection. Uses a HashSet for O(1) lookup. Mirrors Seq.except.
419419
val except : excluded:seq<'T> -> source:AsyncSeq<'T> -> AsyncSeq<'T> when 'T : equality
420420

421+
/// Returns a new asynchronous sequence with the element at the specified index removed.
422+
/// Raises ArgumentException if index is negative. Mirrors Seq.removeAt.
423+
val removeAt : index:int -> source:AsyncSeq<'T> -> AsyncSeq<'T>
424+
425+
/// Returns a new asynchronous sequence with the element at the specified index replaced by the given value.
426+
/// Raises ArgumentException if index is negative. Mirrors Seq.updateAt.
427+
val updateAt : index:int -> value:'T -> source:AsyncSeq<'T> -> AsyncSeq<'T>
428+
429+
/// Returns a new asynchronous sequence with the given value inserted before the element at the specified index.
430+
/// An index equal to the length of the sequence appends the value at the end.
431+
/// Raises ArgumentException if index is negative or greater than the sequence length. Mirrors Seq.insertAt.
432+
val insertAt : index:int -> value:'T -> source:AsyncSeq<'T> -> AsyncSeq<'T>
433+
421434
/// Creates an asynchronous sequence that lazily takes element from an
422435
/// input synchronous sequence and returns them one-by-one.
423436
val ofSeq : source:seq<'T> -> AsyncSeq<'T>
@@ -600,6 +613,11 @@ module AsyncSeq =
600613
/// Returns an empty sequence if the source is empty.
601614
val tail : source:AsyncSeq<'T> -> AsyncSeq<'T>
602615

616+
/// Splits an async sequence at the given index. Returns an async computation that yields
617+
/// the first `count` elements as an array and the remaining elements as a new AsyncSeq.
618+
/// The source is enumerated once; the returned AsyncSeq lazily produces the remainder.
619+
val splitAt : count:int -> source:AsyncSeq<'T> -> Async<'T array * AsyncSeq<'T>>
620+
603621
/// Creates an async computation which iterates the AsyncSeq and collects the output into an array.
604622
val toArrayAsync : source:AsyncSeq<'T> -> Async<'T []>
605623

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

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3486,3 +3486,179 @@ let ``AsyncSeq.rev returns empty sequence for empty input`` () =
34863486
let ``AsyncSeq.rev returns singleton for single element`` () =
34873487
let result = AsyncSeq.rev (asyncSeq { yield 42 }) |> AsyncSeq.toArrayAsync |> Async.RunSynchronously
34883488
Assert.AreEqual([| 42 |], result)
3489+
3490+
[<Test>]
3491+
let ``AsyncSeq.splitAt splits a sequence at the given index`` () =
3492+
let source = asyncSeq { yield 1; yield 2; yield 3; yield 4; yield 5 }
3493+
let first, rest = AsyncSeq.splitAt 3 source |> Async.RunSynchronously
3494+
let restArr = rest |> AsyncSeq.toArrayAsync |> Async.RunSynchronously
3495+
Assert.AreEqual([| 1; 2; 3 |], first)
3496+
Assert.AreEqual([| 4; 5 |], restArr)
3497+
3498+
[<Test>]
3499+
let ``AsyncSeq.splitAt with count=0 returns empty array and full rest`` () =
3500+
let source = asyncSeq { yield 10; yield 20 }
3501+
let first, rest = AsyncSeq.splitAt 0 source |> Async.RunSynchronously
3502+
let restArr = rest |> AsyncSeq.toArrayAsync |> Async.RunSynchronously
3503+
Assert.AreEqual([||], first)
3504+
Assert.AreEqual([| 10; 20 |], restArr)
3505+
3506+
[<Test>]
3507+
let ``AsyncSeq.splitAt with count >= length returns all elements in first and empty rest`` () =
3508+
let source = asyncSeq { yield 1; yield 2; yield 3 }
3509+
let first, rest = AsyncSeq.splitAt 10 source |> Async.RunSynchronously
3510+
let restArr = rest |> AsyncSeq.toArrayAsync |> Async.RunSynchronously
3511+
Assert.AreEqual([| 1; 2; 3 |], first)
3512+
Assert.AreEqual([||], restArr)
3513+
3514+
[<Test>]
3515+
let ``AsyncSeq.splitAt on empty sequence returns empty first and empty rest`` () =
3516+
let first, rest = AsyncSeq.splitAt 3 AsyncSeq.empty<int> |> Async.RunSynchronously
3517+
let restArr = rest |> AsyncSeq.toArrayAsync |> Async.RunSynchronously
3518+
Assert.AreEqual([||], first)
3519+
Assert.AreEqual([||], restArr)
3520+
3521+
[<Test>]
3522+
let ``AsyncSeq.splitAt with count equal to length returns all in first and empty rest`` () =
3523+
let source = asyncSeq { yield 7; yield 8; yield 9 }
3524+
let first, rest = AsyncSeq.splitAt 3 source |> Async.RunSynchronously
3525+
let restArr = rest |> AsyncSeq.toArrayAsync |> Async.RunSynchronously
3526+
Assert.AreEqual([| 7; 8; 9 |], first)
3527+
Assert.AreEqual([||], restArr)
3528+
3529+
[<Test>]
3530+
let ``AsyncSeq.splitAt with negative count throws ArgumentException`` () =
3531+
Assert.Throws<System.ArgumentException>(fun () ->
3532+
AsyncSeq.splitAt -1 AsyncSeq.empty<int> |> Async.RunSynchronously |> ignore) |> ignore
3533+
3534+
// ===== removeAt =====
3535+
3536+
[<Test>]
3537+
let ``AsyncSeq.removeAt removes the element at the specified index`` () =
3538+
let result =
3539+
AsyncSeq.ofSeq [ 1; 2; 3; 4; 5 ]
3540+
|> AsyncSeq.removeAt 2
3541+
|> AsyncSeq.toArrayAsync
3542+
|> Async.RunSynchronously
3543+
Assert.AreEqual([| 1; 2; 4; 5 |], result)
3544+
3545+
[<Test>]
3546+
let ``AsyncSeq.removeAt removes the first element (index 0)`` () =
3547+
let result =
3548+
AsyncSeq.ofSeq [ 10; 20; 30 ]
3549+
|> AsyncSeq.removeAt 0
3550+
|> AsyncSeq.toArrayAsync
3551+
|> Async.RunSynchronously
3552+
Assert.AreEqual([| 20; 30 |], result)
3553+
3554+
[<Test>]
3555+
let ``AsyncSeq.removeAt removes the last element`` () =
3556+
let result =
3557+
AsyncSeq.ofSeq [ 1; 2; 3 ]
3558+
|> AsyncSeq.removeAt 2
3559+
|> AsyncSeq.toArrayAsync
3560+
|> Async.RunSynchronously
3561+
Assert.AreEqual([| 1; 2 |], result)
3562+
3563+
[<Test>]
3564+
let ``AsyncSeq.removeAt raises ArgumentException for negative index`` () =
3565+
Assert.Throws<System.ArgumentException>(fun () ->
3566+
AsyncSeq.ofSeq [ 1; 2; 3 ]
3567+
|> AsyncSeq.removeAt -1
3568+
|> AsyncSeq.toArrayAsync
3569+
|> Async.RunSynchronously |> ignore)
3570+
|> ignore
3571+
3572+
// ===== updateAt =====
3573+
3574+
[<Test>]
3575+
let ``AsyncSeq.updateAt replaces element at specified index`` () =
3576+
let result =
3577+
AsyncSeq.ofSeq [ 1; 2; 3; 4 ]
3578+
|> AsyncSeq.updateAt 1 99
3579+
|> AsyncSeq.toArrayAsync
3580+
|> Async.RunSynchronously
3581+
Assert.AreEqual([| 1; 99; 3; 4 |], result)
3582+
3583+
[<Test>]
3584+
let ``AsyncSeq.updateAt replaces first element`` () =
3585+
let result =
3586+
AsyncSeq.ofSeq [ 1; 2; 3 ]
3587+
|> AsyncSeq.updateAt 0 99
3588+
|> AsyncSeq.toArrayAsync
3589+
|> Async.RunSynchronously
3590+
Assert.AreEqual([| 99; 2; 3 |], result)
3591+
3592+
[<Test>]
3593+
let ``AsyncSeq.updateAt replaces last element`` () =
3594+
let result =
3595+
AsyncSeq.ofSeq [ 1; 2; 3 ]
3596+
|> AsyncSeq.updateAt 2 99
3597+
|> AsyncSeq.toArrayAsync
3598+
|> Async.RunSynchronously
3599+
Assert.AreEqual([| 1; 2; 99 |], result)
3600+
3601+
[<Test>]
3602+
let ``AsyncSeq.updateAt raises ArgumentException for negative index`` () =
3603+
Assert.Throws<System.ArgumentException>(fun () ->
3604+
AsyncSeq.ofSeq [ 1; 2; 3 ]
3605+
|> AsyncSeq.updateAt -1 0
3606+
|> AsyncSeq.toArrayAsync
3607+
|> Async.RunSynchronously |> ignore)
3608+
|> ignore
3609+
3610+
// ===== insertAt =====
3611+
3612+
[<Test>]
3613+
let ``AsyncSeq.insertAt inserts element at specified index`` () =
3614+
let result =
3615+
AsyncSeq.ofSeq [ 1; 2; 4; 5 ]
3616+
|> AsyncSeq.insertAt 2 3
3617+
|> AsyncSeq.toArrayAsync
3618+
|> Async.RunSynchronously
3619+
Assert.AreEqual([| 1; 2; 3; 4; 5 |], result)
3620+
3621+
[<Test>]
3622+
let ``AsyncSeq.insertAt inserts at index 0 (prepend)`` () =
3623+
let result =
3624+
AsyncSeq.ofSeq [ 2; 3 ]
3625+
|> AsyncSeq.insertAt 0 1
3626+
|> AsyncSeq.toArrayAsync
3627+
|> Async.RunSynchronously
3628+
Assert.AreEqual([| 1; 2; 3 |], result)
3629+
3630+
[<Test>]
3631+
let ``AsyncSeq.insertAt appends when index equals sequence length`` () =
3632+
let result =
3633+
AsyncSeq.ofSeq [ 1; 2; 3 ]
3634+
|> AsyncSeq.insertAt 3 4
3635+
|> AsyncSeq.toArrayAsync
3636+
|> Async.RunSynchronously
3637+
Assert.AreEqual([| 1; 2; 3; 4 |], result)
3638+
3639+
[<Test>]
3640+
let ``AsyncSeq.insertAt inserts into empty sequence at index 0`` () =
3641+
let result =
3642+
AsyncSeq.empty<int>
3643+
|> AsyncSeq.insertAt 0 42
3644+
|> AsyncSeq.toArrayAsync
3645+
|> Async.RunSynchronously
3646+
Assert.AreEqual([| 42 |], result)
3647+
3648+
[<Test>]
3649+
let ``AsyncSeq.insertAt raises ArgumentException for negative index`` () =
3650+
Assert.Throws<System.ArgumentException>(fun () ->
3651+
AsyncSeq.ofSeq [ 1; 2; 3 ]
3652+
|> AsyncSeq.insertAt -1 0
3653+
|> AsyncSeq.toArrayAsync
3654+
|> Async.RunSynchronously |> ignore)
3655+
|> ignore
3656+
3657+
[<Test>]
3658+
let ``AsyncSeq.insertAt raises ArgumentException when index exceeds length`` () =
3659+
Assert.Throws<System.ArgumentException>(fun () ->
3660+
AsyncSeq.ofSeq [ 1; 2; 3 ]
3661+
|> AsyncSeq.insertAt 5 0
3662+
|> AsyncSeq.toArrayAsync
3663+
|> Async.RunSynchronously |> ignore)
3664+
|> ignore

version.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<Project>
22
<PropertyGroup>
3-
<Version>4.6.0</Version>
3+
<Version>4.7.0</Version>
44
</PropertyGroup>
55
</Project>

0 commit comments

Comments
 (0)