Skip to content

Commit efa9e4c

Browse files
github-actions[bot]github-actions[bot]Copilotdsyme
authored
[Repo Assist] Add AsyncSeq.removeAt, updateAt, insertAt — 3 new Seq-mirroring combinators (#268)
* Add AsyncSeq.removeAt, updateAt, insertAt — mirror Seq combinators These three F# 6.0 Seq-mirroring combinators enable in-place mutation-style operations on async sequences: - removeAt: skip the element at the given index - updateAt: replace the element at the given index with a new value - insertAt: insert a value before the element at the given index (or append when index == length) All three raise ArgumentException for negative indices. insertAt also raises ArgumentException when index exceeds the sequence length. 14 new tests added; 301/301 pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * ci: trigger checks --------- Co-authored-by: github-actions[bot] <github-actions@github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Don Syme <dsyme@users.noreply.github.com>
1 parent 72d715a commit efa9e4c

File tree

5 files changed

+178
-1
lines changed

5 files changed

+178
-1
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.7.0
2+
3+
* Added `AsyncSeq.removeAt` — returns a new sequence with the element at the specified index removed, mirroring `Seq.removeAt`.
4+
* Added `AsyncSeq.updateAt` — returns a new sequence with the element at the specified index replaced by a given value, mirroring `Seq.updateAt`.
5+
* 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`.
6+
17
### 4.6.0
28

39
* Added `AsyncSeq.isEmpty` — returns `true` if the sequence contains no elements; short-circuits after the first element, mirroring `Seq.isEmpty`.

src/FSharp.Control.AsyncSeq/AsyncSeq.fs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1432,6 +1432,32 @@ module AsyncSeq =
14321432
let s = System.Collections.Generic.HashSet(excluded)
14331433
source |> filter (fun x -> not (s.Contains(x)))
14341434

1435+
let removeAt (index : int) (source : AsyncSeq<'T>) : AsyncSeq<'T> = asyncSeq {
1436+
if index < 0 then invalidArg "index" "must be non-negative"
1437+
let i = ref 0
1438+
for x in source do
1439+
if i.Value <> index then yield x
1440+
i := i.Value + 1 }
1441+
1442+
let updateAt (index : int) (value : 'T) (source : AsyncSeq<'T>) : AsyncSeq<'T> = asyncSeq {
1443+
if index < 0 then invalidArg "index" "must be non-negative"
1444+
let i = ref 0
1445+
for x in source do
1446+
if i.Value = index then yield value
1447+
else yield x
1448+
i := i.Value + 1 }
1449+
1450+
let insertAt (index : int) (value : 'T) (source : AsyncSeq<'T>) : AsyncSeq<'T> = asyncSeq {
1451+
if index < 0 then invalidArg "index" "must be non-negative"
1452+
let i = ref 0
1453+
for x in source do
1454+
if i.Value = index then yield value
1455+
yield x
1456+
i := i.Value + 1
1457+
if i.Value = index then yield value
1458+
elif i.Value < index then
1459+
invalidArg "index" "The index is outside the range of elements in the collection." }
1460+
14351461
#if !FABLE_COMPILER
14361462
let iterAsyncParallel (f:'a -> Async<unit>) (s:AsyncSeq<'a>) : Async<unit> = async {
14371463
use mb = MailboxProcessor.Start (ignore >> async.Return)

src/FSharp.Control.AsyncSeq/AsyncSeq.fsi

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

411+
/// Returns a new asynchronous sequence with the element at the specified index removed.
412+
/// Raises ArgumentException if index is negative. Mirrors Seq.removeAt.
413+
val removeAt : index:int -> source:AsyncSeq<'T> -> AsyncSeq<'T>
414+
415+
/// Returns a new asynchronous sequence with the element at the specified index replaced by the given value.
416+
/// Raises ArgumentException if index is negative. Mirrors Seq.updateAt.
417+
val updateAt : index:int -> value:'T -> source:AsyncSeq<'T> -> AsyncSeq<'T>
418+
419+
/// Returns a new asynchronous sequence with the given value inserted before the element at the specified index.
420+
/// An index equal to the length of the sequence appends the value at the end.
421+
/// Raises ArgumentException if index is negative or greater than the sequence length. Mirrors Seq.insertAt.
422+
val insertAt : index:int -> value:'T -> source:AsyncSeq<'T> -> AsyncSeq<'T>
423+
411424
/// Creates an asynchronous sequence that lazily takes element from an
412425
/// input synchronous sequence and returns them one-by-one.
413426
val ofSeq : source:seq<'T> -> AsyncSeq<'T>

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

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3408,3 +3408,135 @@ let ``AsyncSeq.sortWith sorts descending with negated comparer`` () =
34083408
let ``AsyncSeq.sortWith returns empty array for empty sequence`` () =
34093409
let result = AsyncSeq.sortWith compare AsyncSeq.empty<int>
34103410
Assert.AreEqual([||], result)
3411+
3412+
// ===== removeAt =====
3413+
3414+
[<Test>]
3415+
let ``AsyncSeq.removeAt removes the element at the specified index`` () =
3416+
let result =
3417+
AsyncSeq.ofSeq [ 1; 2; 3; 4; 5 ]
3418+
|> AsyncSeq.removeAt 2
3419+
|> AsyncSeq.toArrayAsync
3420+
|> Async.RunSynchronously
3421+
Assert.AreEqual([| 1; 2; 4; 5 |], result)
3422+
3423+
[<Test>]
3424+
let ``AsyncSeq.removeAt removes the first element (index 0)`` () =
3425+
let result =
3426+
AsyncSeq.ofSeq [ 10; 20; 30 ]
3427+
|> AsyncSeq.removeAt 0
3428+
|> AsyncSeq.toArrayAsync
3429+
|> Async.RunSynchronously
3430+
Assert.AreEqual([| 20; 30 |], result)
3431+
3432+
[<Test>]
3433+
let ``AsyncSeq.removeAt removes the last element`` () =
3434+
let result =
3435+
AsyncSeq.ofSeq [ 1; 2; 3 ]
3436+
|> AsyncSeq.removeAt 2
3437+
|> AsyncSeq.toArrayAsync
3438+
|> Async.RunSynchronously
3439+
Assert.AreEqual([| 1; 2 |], result)
3440+
3441+
[<Test>]
3442+
let ``AsyncSeq.removeAt raises ArgumentException for negative index`` () =
3443+
Assert.Throws<System.ArgumentException>(fun () ->
3444+
AsyncSeq.ofSeq [ 1; 2; 3 ]
3445+
|> AsyncSeq.removeAt -1
3446+
|> AsyncSeq.toArrayAsync
3447+
|> Async.RunSynchronously |> ignore)
3448+
|> ignore
3449+
3450+
// ===== updateAt =====
3451+
3452+
[<Test>]
3453+
let ``AsyncSeq.updateAt replaces element at specified index`` () =
3454+
let result =
3455+
AsyncSeq.ofSeq [ 1; 2; 3; 4 ]
3456+
|> AsyncSeq.updateAt 1 99
3457+
|> AsyncSeq.toArrayAsync
3458+
|> Async.RunSynchronously
3459+
Assert.AreEqual([| 1; 99; 3; 4 |], result)
3460+
3461+
[<Test>]
3462+
let ``AsyncSeq.updateAt replaces first element`` () =
3463+
let result =
3464+
AsyncSeq.ofSeq [ 1; 2; 3 ]
3465+
|> AsyncSeq.updateAt 0 99
3466+
|> AsyncSeq.toArrayAsync
3467+
|> Async.RunSynchronously
3468+
Assert.AreEqual([| 99; 2; 3 |], result)
3469+
3470+
[<Test>]
3471+
let ``AsyncSeq.updateAt replaces last element`` () =
3472+
let result =
3473+
AsyncSeq.ofSeq [ 1; 2; 3 ]
3474+
|> AsyncSeq.updateAt 2 99
3475+
|> AsyncSeq.toArrayAsync
3476+
|> Async.RunSynchronously
3477+
Assert.AreEqual([| 1; 2; 99 |], result)
3478+
3479+
[<Test>]
3480+
let ``AsyncSeq.updateAt raises ArgumentException for negative index`` () =
3481+
Assert.Throws<System.ArgumentException>(fun () ->
3482+
AsyncSeq.ofSeq [ 1; 2; 3 ]
3483+
|> AsyncSeq.updateAt -1 0
3484+
|> AsyncSeq.toArrayAsync
3485+
|> Async.RunSynchronously |> ignore)
3486+
|> ignore
3487+
3488+
// ===== insertAt =====
3489+
3490+
[<Test>]
3491+
let ``AsyncSeq.insertAt inserts element at specified index`` () =
3492+
let result =
3493+
AsyncSeq.ofSeq [ 1; 2; 4; 5 ]
3494+
|> AsyncSeq.insertAt 2 3
3495+
|> AsyncSeq.toArrayAsync
3496+
|> Async.RunSynchronously
3497+
Assert.AreEqual([| 1; 2; 3; 4; 5 |], result)
3498+
3499+
[<Test>]
3500+
let ``AsyncSeq.insertAt inserts at index 0 (prepend)`` () =
3501+
let result =
3502+
AsyncSeq.ofSeq [ 2; 3 ]
3503+
|> AsyncSeq.insertAt 0 1
3504+
|> AsyncSeq.toArrayAsync
3505+
|> Async.RunSynchronously
3506+
Assert.AreEqual([| 1; 2; 3 |], result)
3507+
3508+
[<Test>]
3509+
let ``AsyncSeq.insertAt appends when index equals sequence length`` () =
3510+
let result =
3511+
AsyncSeq.ofSeq [ 1; 2; 3 ]
3512+
|> AsyncSeq.insertAt 3 4
3513+
|> AsyncSeq.toArrayAsync
3514+
|> Async.RunSynchronously
3515+
Assert.AreEqual([| 1; 2; 3; 4 |], result)
3516+
3517+
[<Test>]
3518+
let ``AsyncSeq.insertAt inserts into empty sequence at index 0`` () =
3519+
let result =
3520+
AsyncSeq.empty<int>
3521+
|> AsyncSeq.insertAt 0 42
3522+
|> AsyncSeq.toArrayAsync
3523+
|> Async.RunSynchronously
3524+
Assert.AreEqual([| 42 |], result)
3525+
3526+
[<Test>]
3527+
let ``AsyncSeq.insertAt raises ArgumentException for negative index`` () =
3528+
Assert.Throws<System.ArgumentException>(fun () ->
3529+
AsyncSeq.ofSeq [ 1; 2; 3 ]
3530+
|> AsyncSeq.insertAt -1 0
3531+
|> AsyncSeq.toArrayAsync
3532+
|> Async.RunSynchronously |> ignore)
3533+
|> ignore
3534+
3535+
[<Test>]
3536+
let ``AsyncSeq.insertAt raises ArgumentException when index exceeds length`` () =
3537+
Assert.Throws<System.ArgumentException>(fun () ->
3538+
AsyncSeq.ofSeq [ 1; 2; 3 ]
3539+
|> AsyncSeq.insertAt 5 0
3540+
|> AsyncSeq.toArrayAsync
3541+
|> Async.RunSynchronously |> ignore)
3542+
|> 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)