Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions release-notes.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ Release notes:

1.0.0
- adds taskSeqDynamic computation expression and TaskSeqDynamic/TaskSeqDynamicInfo types for dynamic (FSI-compatible) resumable code, fixing issue where taskSeq would raise NotImplementedException in F# Interactive, #246
- perf: simplify iter, fold, reduce, mapFold, tryLast, skipOrTake (Drop/Truncate) to use while! and remove manual go-flag and initial MoveNextAsync pre-advance, matching the pattern already used by sum/sumBy/average
- perf: TaskSeq.chunkBy and chunkByAsync reuse the ResizeArray buffer between chunks, reducing allocations on sequences with many chunk boundaries
- fixes: TaskSeq.insertAt, insertManyAt, removeAt, removeManyAt, updateAt now raise ArgumentNullException (not NullReferenceException) when given a null source; insertManyAt also validates the values argument
- refactor: simplify lengthBy and lengthBeforeMax to use while! and remove the redundant mutable 'go' and initial MoveNextAsync
Expand Down
113 changes: 34 additions & 79 deletions src/FSharp.Control.TaskSeq/TaskSeqInternal.fs
Original file line number Diff line number Diff line change
Expand Up @@ -357,68 +357,49 @@ module internal TaskSeqInternal =

task {
use e = source.GetAsyncEnumerator CancellationToken.None
let mutable go = true
let! step = e.MoveNextAsync()
go <- step

// this ensures that the inner loop is optimized for the closure
// though perhaps we need to split into individual functions after all to use
// InlineIfLambda?
// Each branch keeps its own while! loop so the match dispatch is hoisted out and
// the JIT sees a tight, single-case loop (same pattern as sum/sumBy etc.).
match action with
| CountableAction action ->
let mutable i = 0

while go do
do action i e.Current
let! step = e.MoveNextAsync()
while! e.MoveNextAsync() do
action i e.Current
i <- i + 1
go <- step

| SimpleAction action ->
while go do
do action e.Current
let! step = e.MoveNextAsync()
go <- step
while! e.MoveNextAsync() do
action e.Current

| AsyncCountableAction action ->
let mutable i = 0

while go do
while! e.MoveNextAsync() do
do! action i e.Current
let! step = e.MoveNextAsync()
i <- i + 1
go <- step

| AsyncSimpleAction action ->
while go do
while! e.MoveNextAsync() do
do! action e.Current
let! step = e.MoveNextAsync()
go <- step
}

let fold folder initial (source: TaskSeq<_>) =
checkNonNull (nameof source) source

task {
use e = source.GetAsyncEnumerator CancellationToken.None
let mutable go = true
let mutable result = initial
let! step = e.MoveNextAsync()
go <- step

match folder with
| FolderAction folder ->
while go do
while! e.MoveNextAsync() do
result <- folder result e.Current
let! step = e.MoveNextAsync()
go <- step

| AsyncFolderAction folder ->
while go do
while! e.MoveNextAsync() do
let! tempResult = folder result e.Current
result <- tempResult
let! step = e.MoveNextAsync()
go <- step

return result
}
Expand Down Expand Up @@ -457,22 +438,16 @@ module internal TaskSeqInternal =
raiseEmptySeq ()

let mutable result = e.Current
let! step = e.MoveNextAsync()
let mutable go = step

match folder with
| FolderAction folder ->
while go do
while! e.MoveNextAsync() do
result <- folder result e.Current
let! step = e.MoveNextAsync()
go <- step

| AsyncFolderAction folder ->
while go do
while! e.MoveNextAsync() do
let! tempResult = folder result e.Current
result <- tempResult
let! step = e.MoveNextAsync()
go <- step

return result
}
Expand All @@ -482,28 +457,21 @@ module internal TaskSeqInternal =

task {
use e = source.GetAsyncEnumerator CancellationToken.None
let mutable go = true
let mutable state = initial
let results = ResizeArray()
let! step = e.MoveNextAsync()
go <- step

match folder with
| MapFolderAction folder ->
while go do
while! e.MoveNextAsync() do
let result, newState = folder state e.Current
results.Add result
state <- newState
let! step = e.MoveNextAsync()
go <- step

| AsyncMapFolderAction folder ->
while go do
while! e.MoveNextAsync() do
let! (result, newState) = folder state e.Current
results.Add result
state <- newState
let! step = e.MoveNextAsync()
go <- step

return results.ToArray(), state
}
Expand Down Expand Up @@ -804,15 +772,10 @@ module internal TaskSeqInternal =

task {
use e = source.GetAsyncEnumerator CancellationToken.None
let mutable go = true
let mutable last = ValueNone
let! step = e.MoveNextAsync()
go <- step

while go do
while! e.MoveNextAsync() do
last <- ValueSome e.Current
let! step = e.MoveNextAsync()
go <- step

match last with
| ValueSome value -> return Some value
Expand Down Expand Up @@ -1233,24 +1196,19 @@ module internal TaskSeqInternal =
else
taskSeq {
use e = source.GetAsyncEnumerator CancellationToken.None
let mutable i = 0
let mutable cont = true

let! step = e.MoveNextAsync()
let mutable cont = step
let mutable pos = 0

// skip, or stop looping if we reached the end
while cont do
pos <- pos + 1

if pos < count then
let! moveNext = e.MoveNextAsync()
cont <- moveNext
else
cont <- false
// advance past 'count' elements; stop early if the source is shorter
while cont && i < count do
let! hasMore = e.MoveNextAsync()
if hasMore then i <- i + 1 else cont <- false

// return the rest
while! e.MoveNextAsync() do
yield e.Current
// return remaining elements; enumerator is at element (count-1) so one
// more MoveNext is needed to reach element (count)
if cont then
while! e.MoveNextAsync() do
yield e.Current

}
| Take ->
Expand All @@ -1277,19 +1235,16 @@ module internal TaskSeqInternal =
else
taskSeq {
use e = source.GetAsyncEnumerator CancellationToken.None
let mutable yielded = 0
let mutable cont = true

let! step = e.MoveNextAsync()
let mutable cont = step
let mutable pos = 0

// return items until we've exhausted the seq
while cont do
yield e.Current
pos <- pos + 1
// yield up to 'count' elements; stop when exhausted or limit reached
while cont && yielded < count do
let! hasMore = e.MoveNextAsync()

if pos < count then
let! moveNext = e.MoveNextAsync()
cont <- moveNext
if hasMore then
yield e.Current
yielded <- yielded + 1
else
cont <- false

Expand Down