Skip to content

Commit daacc7e

Browse files
perf: replace ref cells with mutable in singleton, collectSeq, takeWhileInclusive, takeWhileInclusiveAsync
Modernises 4 remaining functions that still used old-style ref cells (! / := operators) to use mutable local variables instead. This eliminates heap-allocated Ref<T> objects in these enumerators, reducing GC pressure. - singleton: state ref 0 -> mutable state = 0 - collectSeq: state ref (CollectSeqState...) -> mutable state - takeWhileInclusive: fin ref false -> mutable fin = false - takeWhileInclusiveAsync: fin ref false -> mutable fin = false All 402 tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 54b8258 commit daacc7e

File tree

2 files changed

+18
-17
lines changed

2 files changed

+18
-17
lines changed

RELEASE_NOTES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
### 4.12.0
22

33
* Tests: Added tests for `mapiAsync`, `tryPickAsync`, `pickAsync`, and `groupByAsync` — these four async functions previously had no test coverage.
4+
* Performance: Modernised `singleton`, `collectSeq`, `takeWhileInclusive`, and `takeWhileInclusiveAsync` to use `mutable` local variables instead of `ref` cells (`!`/`:=` operators). Eliminates heap-allocated `Ref<T>` objects in these enumerators, reducing GC pressure consistent with the improvements in 4.11.0.
45
* Added `AsyncSeq.tryFindBack` — returns the last element for which the predicate returns true, or `None` if no match. Mirrors `Array.tryFindBack` / `List.tryFindBack`.
56
* Added `AsyncSeq.tryFindBackAsync` — async-predicate variant of `tryFindBack`.
67
* Added `AsyncSeq.findBack` — returns the last element for which the predicate returns true; raises `KeyNotFoundException` if no match. Mirrors `Array.findBack` / `List.findBack`.

src/FSharp.Control.AsyncSeq/AsyncSeq.fs

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -450,11 +450,11 @@ module AsyncSeq =
450450

451451
let singleton (v:'T) : AsyncSeq<'T> =
452452
AsyncSeqImpl(fun () ->
453-
let state = ref 0
453+
let mutable state = 0
454454
{ new IAsyncSeqEnumerator<'T> with
455455
member _.MoveNext() = async {
456-
let res = state.Value = 0
457-
incr state
456+
let res = state = 0
457+
state <- state + 1
458458
return (if res then Some v else None) }
459459
interface System.IDisposable with
460460
member _.Dispose() = () }) :> AsyncSeq<'T>
@@ -719,20 +719,20 @@ module AsyncSeq =
719719
// Like collect, but the input is a sequence, where no bind is required on each step of the enumeration
720720
let collectSeq (f: 'T -> AsyncSeq<'U>) (inp: seq<'T>) : AsyncSeq<'U> =
721721
AsyncSeqImpl(fun () ->
722-
let state = ref (CollectSeqState.NotStarted inp)
722+
let mutable state = CollectSeqState.NotStarted inp
723723
{ new IAsyncSeqEnumerator<'U> with
724724
member x.MoveNext() =
725-
async { match !state with
725+
async { match state with
726726
| CollectSeqState.NotStarted inp ->
727727
return!
728728
(let e1 = inp.GetEnumerator()
729-
state := CollectSeqState.HaveInputEnumerator e1
729+
state <- CollectSeqState.HaveInputEnumerator e1
730730
x.MoveNext())
731731
| CollectSeqState.HaveInputEnumerator e1 ->
732732
return!
733733
(if e1.MoveNext() then
734734
let e2 = (f e1.Current).GetEnumerator()
735-
state := CollectSeqState.HaveInnerEnumerator (e1, e2)
735+
state <- CollectSeqState.HaveInnerEnumerator (e1, e2)
736736
else
737737
x.Dispose()
738738
x.MoveNext())
@@ -741,19 +741,19 @@ module AsyncSeq =
741741
match res2 with
742742
| None ->
743743
return!
744-
(state := CollectSeqState.HaveInputEnumerator e1
744+
(state <- CollectSeqState.HaveInputEnumerator e1
745745
dispose e2
746746
x.MoveNext())
747747
| Some _ ->
748748
return res2
749749
| _ -> return None}
750750
member x.Dispose() =
751-
match !state with
751+
match state with
752752
| CollectSeqState.HaveInputEnumerator e1 ->
753-
state := CollectSeqState.Finished
753+
state <- CollectSeqState.Finished
754754
dispose e1
755755
| CollectSeqState.HaveInnerEnumerator (e1, e2) ->
756-
state := CollectSeqState.Finished
756+
state <- CollectSeqState.Finished
757757
dispose e2
758758
dispose e1
759759
x.Dispose()
@@ -1982,35 +1982,35 @@ module AsyncSeq =
19821982
let takeWhileInclusive (f : 'a -> bool) (s : AsyncSeq<'a>) : AsyncSeq<'a> =
19831983
AsyncSeqImpl(fun () ->
19841984
let en = s.GetEnumerator()
1985-
let fin = ref false
1985+
let mutable fin = false
19861986
{ new IAsyncSeqEnumerator<'a> with
19871987
member _.MoveNext() = async {
1988-
if !fin then return None
1988+
if fin then return None
19891989
else
19901990
let! next = en.MoveNext()
19911991
match next with
19921992
| None -> return None
19931993
| Some a ->
19941994
if f a then return Some a
1995-
else fin := true; return Some a }
1995+
else fin <- true; return Some a }
19961996
interface System.IDisposable with
19971997
member _.Dispose() = en.Dispose() }) :> AsyncSeq<'a>
19981998

19991999
let takeWhileInclusiveAsync (predicate: 'T -> Async<bool>) (source: AsyncSeq<'T>) : AsyncSeq<'T> =
20002000
AsyncSeqImpl(fun () ->
20012001
let en = source.GetEnumerator()
2002-
let fin = ref false
2002+
let mutable fin = false
20032003
{ new IAsyncSeqEnumerator<'T> with
20042004
member _.MoveNext() = async {
2005-
if !fin then return None
2005+
if fin then return None
20062006
else
20072007
let! next = en.MoveNext()
20082008
match next with
20092009
| None -> return None
20102010
| Some a ->
20112011
let! ok = predicate a
20122012
if ok then return Some a
2013-
else fin := true; return Some a }
2013+
else fin <- true; return Some a }
20142014
interface System.IDisposable with
20152015
member _.Dispose() = en.Dispose() }) :> AsyncSeq<'T>
20162016

0 commit comments

Comments
 (0)