From baa3eb0e865d4a936a93a0d5c6d0cea7f0b2c850 Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Sun, 13 Nov 2022 01:31:57 +0100 Subject: [PATCH 01/68] Update nuget description --- src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj b/src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj index b0b89c51..4287c5e9 100644 --- a/src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj +++ b/src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj @@ -1,4 +1,4 @@ - + netstandard2.1 @@ -8,11 +8,11 @@ Computation expression 'taskSeq' for processing IAsyncEnumerable sequences and module functions $(Version) Abel Braaksma; Don Syme - This library brings C#'s concept of 'await foreach' to F#. + This library brings C#'s concept of 'await foreach' to F#, with a seamless implementation of IAsyncEnumerable<'T>. -The 'taskSeq' computation expression adds support for awaitable asyncronous sequences with a similar ease of use and performance as F#'s 'task' CE. TaskSeq brings 'seq' and 'task' together in a safe way. +The 'taskSeq' computation expression adds support for awaitable asyncronous sequences with a similar ease of use and performance as F#'s 'task' CE, with minimal overhead through ValueTask under the hood. TaskSeq brings 'seq' and 'task' together in a safe way. -Generates optimized IL code and comes with a comprehensive set of module functions. See README for more info. +Generates optimized IL code through the new resumable state machines, and comes with a comprehensive set of helpful functions in module 'TaskSeq'. See README for documentation and more info. Copyright 2022 https://github.com/fsprojects/FSharp.Control.TaskSeq https://github.com/fsprojects/FSharp.Control.TaskSeq From 5fb32ec510710298de143e30208dbe897314dce1 Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Sun, 13 Nov 2022 01:59:55 +0100 Subject: [PATCH 02/68] Update readmes again --- README.md | 145 +++++++++++++++++++++++++++++---- assets/nuget-package-readme.md | 15 ++-- 2 files changed, 138 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 7da1e2fe..35054f13 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,10 @@ [![build][buildstatus_img]][buildstatus] [![test][teststatus_img]][teststatus] +[![Nuget](https://img.shields.io/nuget/vpre/FSharp.Control.TaskSeq)](https://www.nuget.org/packages/FSharp.Control.TaskSeq/) # TaskSeq -An implementation [`IAsyncEnumerable<'T>`][3] as a `taskSeq` CE for F# with accompanying `TaskSeq` module. - -The `IAsyncEnumerable` interface was added to .NET in `.NET Core 3.0` and is part of `.NET Standard 2.1`. The main use-case was for iterative asynchronous enumeration over some resource. For instance, an event stream or a REST API interface with pagination, where each page is a [`MoveNextAsync`][4] call on the [`IAsyncEnumerator<'T>`][5] given by a call to [`GetAsyncEnumerator()`][6]. It has been relatively challenging to work properly with this type and dealing with each step being asynchronous, and the enumerator implementing [`IAsyncDisposable`][7] as well, which requires careful handling. +An implementation of [`IAsyncEnumerable<'T>`][3] as a computation expression: `taskSeq { ... }` with an accompanying `TaskSeq` module. ----------------------------------------- @@ -18,10 +17,15 @@ The `IAsyncEnumerable` interface was added to .NET in `.NET Core 3.0` and is par More info: https://marketplace.visualstudio.com/items?itemName=yzhang.markdown-all-in-one#table-of-contents --> -- [Feature planning](#feature-planning) -- [Implementation progress](#implementation-progress) - - [`taskSeq` CE](#taskseq-ce) - - [`TaskSeq` module functions](#taskseq-module-functions) +- [Overview](#overview) + - [Module functions](#module-functions) + - [`taskSeq` computation expressions](#taskseq-computation-expressions) + - [Installation](#installation) + - [Examples](#examples) +- [Status & planning](#status--planning) + - [Implementation progress](#implementation-progress) + - [Progress `taskSeq` CE](#progress-taskseq-ce) + - [Progress and implemented `TaskSeq` module functions](#progress-and-implemented-taskseq-module-functions) - [More information](#more-information) - [Futher reading `IAsyncEnumerable`](#futher-reading-iasyncenumerable) - [Futher reading on resumable state machines](#futher-reading-on-resumable-state-machines) @@ -38,27 +42,134 @@ The `IAsyncEnumerable` interface was added to .NET in `.NET Core 3.0` and is par ----------------------------------------- -## Feature planning +## Overview + +The `IAsyncEnumerable` interface was added to .NET in `.NET Core 3.0` and is part of `.NET Standard 2.1`. The main use-case was for iterative asynchronous enumeration over some resource. For instance, an event stream or a REST API interface with pagination, asynchronous reading over a list of files and accumulating the results, where each action can be modeled as a [`MoveNextAsync`][4] call on the [`IAsyncEnumerator<'T>`][5] given by a call to [`GetAsyncEnumerator()`][6]. + +Since the introduction of `task` in F# the call for a native implementation of _task sequences_ has grown, in particular because proper iterating over an `IAsyncEnumerable` has proven challenging, especially if one wants to avoid mutable variables. This library is an answer to that call and implements the same _resumable state machine_ approach with `taskSeq`. + +### Module functions + +As with `seq` and `Seq`, this library comes with a bunch of well-known collection functions, like `TaskSeq.empty`, `isEmpty` or `TaskSeq.map`, `iter`, `collect`, `fold` and `TaskSeq.find`, `pick`, `choose`, `filter`. Where applicable, these come with async variants, like `TaskSeq.mapAsync` `iterAsync`, `collectAsync`, `foldAsync` and `TaskSeq.findAsync`, `pickAsync`, `chooseAsync`, `filterAsync`, which allows the applied function to be asynchronous. + +[See below](#current-set-of-taskseq-utility-functions) for a full list of currently implemented functions and their variants. + +### `taskSeq` computation expressions + +The `taskSeq` computation expression can be used just like using `seq`. On top of that, it adds support for working with tasks through `let!` and +looping over a normal or asynchronous sequence (one that implements `IAsyncEnumerable<'T>'`). You can use `yield!` and `yield` and there's support +for `use` and `use!`, `try-with` and `try-finally` and `while` loops within the task sequence expression: + +### Installation -Not necessarily in order of importance: +Dotnet Nuget + +```cmd +dotnet add package FSharp.Control.TaskSeq +``` + +For a specific project: + +```cmd +dotnet add myproject.fsproj package FSharp.Control.TaskSeq +``` + +F# Interactive (FSI): + +```f# +// latest version +> #r "nuget: FSharp.Control.TaskSeq" + +// or with specific version +> #r "nuget: FSharp.Control.TaskSeq, 0.2.2" +``` + +Paket: + +```cmd +dotnet paket add FSharp.Control.TaskSeq --project +``` + +Package Manager: + +```cmd +PM> NuGet\Install-Package FSharp.Control.TaskSeq +``` + +As package reference in `fsproj` or `csproj` file: + +```xml + + +``` + +### Examples + +```f# +open System.IO + +open FSharp.Control + +// singleton is fine +let hello = taskSeq { yield "Hello, World!" } + +// can be mixed with normal sequences +let oneToTen = taskSeq { yield! [1..10] } + +// returns a delayed sequence of IAsyncEnumerable +let allFilesAsLines() = taskSeq { + let files = Directory.EnumerateFiles(@"c:\temp") + for file in files do + // await + let! contents = File.ReadAllLinesAsync file + // return all lines + yield! contents +} + +let write file = + allFilesAsLines() + + // synchronous map function on asynchronous task sequence + |> TaskSeq.map (fun x -> x.Replace("a", "b")) + + // asynchronous map + |> TaskSeq.mapAsync (fun x -> task { return "hello: " + x }) + + // asynchronous iter + |> TaskSeq.iterAsync (fun data -> File.WriteAllTextAsync(fileName, data)) + + +// infinite sequence +let feedFromTwitter user pwd = taskSeq { + do! loginToTwitterAsync(user, pwd) + while true do + let! message = getNextNextTwitterMessageAsync() + yield message +} +``` + +## Status & planning + +This project has stable features currently, but before we go full "version one", we'd like to complete the surface area. This section covers the status of that, with a full list of implmented functions below. Here's the short list: - [x] Stabilize and battle-test `taskSeq` resumable code. **DONE** - [x] A growing set of module functions `TaskSeq`, see below for progress. **DONE & IN PROGRESS** - [x] Packaging and publishing on Nuget, **DONE, PUBLISHED SINCE: 7 November 2022**. See https://www.nuget.org/packages/FSharp.Control.TaskSeq - [x] Add `Async` variants for functions taking HOF arguments. **DONE** - [ ] Add generated docs to -- [ ] Expand surface area based on `AsyncSeq`. -- [ ] User requests? +- [ ] Expand surface area based on `AsyncSeq`. **ONGOING** + +### Implementation progress -## Implementation progress +As of 9 November 2022: [Nuget package available][21]. In this phase, we will frequently update the package. Current: -As of 6 November 2022: +[![Nuget](https://img.shields.io/nuget/vpre/FSharp.Control.TaskSeq)](https://www.nuget.org/packages/FSharp.Control.TaskSeq/) -### `taskSeq` CE +### Progress `taskSeq` CE -The _resumable state machine_ backing the `taskSeq` CE is now finished and _restartability_ (not to be confused with _resumability_) has been implemented and stabilized. Full support for empty task sequences is done. Focus is now on adding functionality there, like adding more useful overloads for `yield` and `let!`. Suggestions are welcome! +The _resumable state machine_ backing the `taskSeq` CE is now finished and _restartability_ (not to be confused with _resumability_) has been implemented and stabilized. Full support for empty task sequences is done. Focus is now on adding functionality there, like adding more useful overloads for `yield` and `let!`. [Suggestions are welcome!][issues]. -### `TaskSeq` module functions +### Progress and implemented `TaskSeq` module functions We are working hard on getting a full set of module functions on `TaskSeq` that can be used with `IAsyncEnumerable` sequences. Our guide is the set of F# `Seq` functions in F# Core and, where applicable, the functions provided from `AsyncSeq`. Each implemented function is documented through XML doc comments to provide the necessary context-sensitive help. @@ -600,6 +711,7 @@ module TaskSeq = [18]: https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/computation-expressions [19]: https://fsharpforfunandprofit.com/series/computation-expressions/ [20]: https://github.com/dotnet/fsharp/blob/d5312aae8aad650f0043f055bb14c3aa8117e12e/tests/benchmarks/CompiledCodeBenchmarks/TaskPerf/TaskPerf/taskSeq.fs +[21]: https://www.nuget.org/packages/FSharp.Control.TaskSeq#versions-body-tab [#2]: https://github.com/fsprojects/FSharp.Control.TaskSeq/pull/2 [#11]: https://github.com/fsprojects/FSharp.Control.TaskSeq/pull/11 @@ -614,3 +726,4 @@ module TaskSeq = [#82]: https://github.com/fsprojects/FSharp.Control.TaskSeq/pull/82 [#83]: https://github.com/fsprojects/FSharp.Control.TaskSeq/pull/83 +[issues]: https://github.com/fsprojects/FSharp.Control.TaskSeq/issues \ No newline at end of file diff --git a/assets/nuget-package-readme.md b/assets/nuget-package-readme.md index dd3a5aa6..6fa010bf 100644 --- a/assets/nuget-package-readme.md +++ b/assets/nuget-package-readme.md @@ -14,6 +14,7 @@ An implementation of [`IAsyncEnumerable<'T>`][3] as a computation expression: `t --> - [Overview](#overview) + - [Module functions](#module-functions) - [`taskSeq` computation expressions](#taskseq-computation-expressions) - [Examples](#examples) - [`TaskSeq` module functions](#taskseq-module-functions) @@ -26,19 +27,21 @@ An implementation of [`IAsyncEnumerable<'T>`][3] as a computation expression: `t ## Overview -The `IAsyncEnumerable` interface was added to .NET in `.NET Core 3.0` and is part of `.NET Standard 2.1`. The main use-case was for iterative asynchronous enumeration over some resource. For instance, an event stream or a REST API interface with pagination, asynchronous reading over a list of files and accumulating the results, where each action can be modeled as a [`MoveNextAsync`][4] call on the [`IAsyncEnumerator<'T>`][5] given by a call to [`GetAsyncEnumerator()`][6]. +The `IAsyncEnumerable` interface was added to .NET in `.NET Core 3.0` and is part of `.NET Standard 2.1`. The main use-case was for iterative asynchronous enumeration over some resource. For instance, an event stream or a REST API interface with pagination, asynchronous reading over a list of files and accumulating the results, where each action can be modeled as a [`MoveNextAsync`][4] call on the [`IAsyncEnumerator<'T>`][5] given by a call to [`GetAsyncEnumerator()`][6]. Since the introduction of `task` in F# the call for a native implementation of _task sequences_ has grown, in particular because proper iterating over an `IAsyncEnumerable` has proven challenging, especially if one wants to avoid mutable variables. This library is an answer to that call and implements the same _resumable state machine_ approach with `taskSeq`. -As with `seq` and `Seq`, this library comes with a bunch of well-known collection functions, like `TaskSeq.empty/isEmpty` or `TaskSeq.map/iter/collect` and `TaskSeq.find/pick/choose/filter`. Where applicable, these come with async variants, like `TaskSeq.mapAsync/iterAsync/collectAsync` and `TaskSeq.findAsync/pickAsync/chooseAsync/filterAsync`, which allows the apply function to be asynchronous. +### Module functions -See below for a full list of currently implemented functions. +As with `seq` and `Seq`, this library comes with a bunch of well-known collection functions, like `TaskSeq.empty`, `isEmpty` or `TaskSeq.map`, `iter`, `collect`, `fold` and `TaskSeq.find`, `pick`, `choose`, `filter`. Where applicable, these come with async variants, like `TaskSeq.mapAsync` `iterAsync`, `collectAsync`, `foldAsync` and `TaskSeq.findAsync`, `pickAsync`, `chooseAsync`, `filterAsync`, which allows the applied function to be asynchronous. + +[See below](#taskseq-module-functions) for a full list of currently implemented functions and their variants. ### `taskSeq` computation expressions -The `taskSeq` computation expression can be used just like using `seq`. On top of that, it adds support for working with tasks through `let!` and +The `taskSeq` computation expression can be used just like using `seq`. On top of that, it adds support for working with tasks through `let!` and looping over a normal or asynchronous sequence (one that implements `IAsyncEnumerable<'T>'`). You can use `yield!` and `yield` and there's support -for `use` and `use!`, `try-with` and `try-finally` and `while` loops within the task sequence expression: +for `use` and `use!`, `try-with` and `try-finally` and `while` loops within the task sequence expression. ### Examples @@ -48,7 +51,7 @@ open System.IO open FSharp.Control // singleton is fine -let hello = taskSeq { yield "Hello, World!"" } +let hello = taskSeq { yield "Hello, World!" } // can be mixed with normal sequences let oneToTen = taskSeq { yield! [1..10] } From 836fb735b70d72c1715501d16435e2bb0478ac16 Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Sun, 13 Nov 2022 02:46:15 +0100 Subject: [PATCH 03/68] Update current set of implemented functions of the TaskSeq surface area --- README.md | 350 ++++++++++++------------------------------------------ 1 file changed, 78 insertions(+), 272 deletions(-) diff --git a/README.md b/README.md index 35054f13..126493b2 100644 --- a/README.md +++ b/README.md @@ -394,8 +394,7 @@ Command modifiers, like `release` and `debug`, can be specified with `-` or `/` build help ``` -For more info, see this PR: https://github.com/abelbraaksma/TaskSeq/pull/29. - +For more info, see this PR: . ## Work in progress @@ -405,285 +404,92 @@ On top of that, this library adds a set of `TaskSeq` module functions, with thei ## Current set of `TaskSeq` utility functions -The following is the current surface area of the `TaskSeq` utility functions. This is just a dump of the signatures with doc comments -to be used as a quick ref. +The following are the current surface area of the `TaskSeq` utility functions, ordered alphabetically. ```f# module TaskSeq = - open System.Collections.Generic - open System.Threading.Tasks - open FSharp.Control.TaskSeqBuilders - - /// Initialize an empty taskSeq. - val empty<'T> : taskSeq<'T> - - /// - /// Returns if the task sequence contains no elements, otherwise. - /// - val isEmpty: taskSeq: taskSeq<'T> -> Task - - /// Returns taskSeq as an array. This function is blocking until the sequence is exhausted and will properly dispose of the resources. - val toList: t: taskSeq<'T> -> 'T list - - /// Returns taskSeq as an array. This function is blocking until the sequence is exhausted and will properly dispose of the resources. - val toArray: taskSeq: taskSeq<'T> -> 'T[] - - /// Returns taskSeq as a seq, similar to Seq.cached. This function is blocking until the sequence is exhausted and will properly dispose of the resources. - val toSeqCached: taskSeq: taskSeq<'T> -> seq<'T> - - /// Unwraps the taskSeq as a Task>. This function is non-blocking. - val toArrayAsync: taskSeq: taskSeq<'T> -> Task<'T[]> - - /// Unwraps the taskSeq as a Task>. This function is non-blocking. - val toListAsync: taskSeq: taskSeq<'T> -> Task<'T list> - - /// Unwraps the taskSeq as a Task>. This function is non-blocking. - val toResizeArrayAsync: taskSeq: taskSeq<'T> -> Task> - - /// Unwraps the taskSeq as a Task>. This function is non-blocking. - val toIListAsync: taskSeq: taskSeq<'T> -> Task> - - /// Unwraps the taskSeq as a Task>. This function is non-blocking, - /// exhausts the sequence and caches the results of the tasks in the sequence. - val toSeqCachedAsync: taskSeq: taskSeq<'T> -> Task> - - /// Create a taskSeq of an array. - val ofArray: array: 'T[] -> taskSeq<'T> - - /// Create a taskSeq of a list. - val ofList: list: 'T list -> taskSeq<'T> - - /// Create a taskSeq of a seq. - val ofSeq: sequence: seq<'T> -> taskSeq<'T> - - /// Create a taskSeq of a ResizeArray, aka List. - val ofResizeArray: data: ResizeArray<'T> -> taskSeq<'T> - - /// Create a taskSeq of a sequence of tasks, that may already have hot-started. - val ofTaskSeq: sequence: seq<#Task<'T>> -> taskSeq<'T> - - /// Create a taskSeq of a list of tasks, that may already have hot-started. - val ofTaskList: list: #Task<'T> list -> taskSeq<'T> - - /// Create a taskSeq of an array of tasks, that may already have hot-started. - val ofTaskArray: array: #Task<'T> array -> taskSeq<'T> - - /// Create a taskSeq of a seq of async. - val ofAsyncSeq: sequence: seq> -> taskSeq<'T> - - /// Create a taskSeq of a list of async. - val ofAsyncList: list: Async<'T> list -> taskSeq<'T> - - /// Create a taskSeq of an array of async. - val ofAsyncArray: array: Async<'T> array -> taskSeq<'T> - - /// Iterates over the taskSeq applying the action function to each item. This function is non-blocking - /// exhausts the sequence as soon as the task is evaluated. - val iter: action: ('T -> unit) -> taskSeq: taskSeq<'T> -> Task - - /// Iterates over the taskSeq applying the action function to each item. This function is non-blocking, - /// exhausts the sequence as soon as the task is evaluated. - val iteri: action: (int -> 'T -> unit) -> taskSeq: taskSeq<'T> -> Task - - /// Iterates over the taskSeq applying the async action to each item. This function is non-blocking - /// exhausts the sequence as soon as the task is evaluated. - val iterAsync: action: ('T -> #Task) -> taskSeq: taskSeq<'T> -> Task - - /// Iterates over the taskSeq, applying the async action to each item. This function is non-blocking, - /// exhausts the sequence as soon as the task is evaluated. - val iteriAsync: action: (int -> 'T -> #Task) -> taskSeq: taskSeq<'T> -> Task - - /// Maps over the taskSeq, applying the mapper function to each item. This function is non-blocking. - val map: mapper: ('T -> 'U) -> taskSeq: taskSeq<'T> -> taskSeq<'U> - - /// Maps over the taskSeq with an index, applying the mapper function to each item. This function is non-blocking. - val mapi: mapper: (int -> 'T -> 'U) -> taskSeq: taskSeq<'T> -> taskSeq<'U> - - /// Maps over the taskSeq, applying the async mapper function to each item. This function is non-blocking. - val mapAsync: mapper: ('T -> #Task<'U>) -> taskSeq: taskSeq<'T> -> taskSeq<'U> - - /// Maps over the taskSeq with an index, applying the async mapper function to each item. This function is non-blocking. - val mapiAsync: mapper: (int -> 'T -> #Task<'U>) -> taskSeq: taskSeq<'T> -> taskSeq<'U> - - /// Applies the given function to the items in the taskSeq and concatenates all the results in order. - val collect: binder: ('T -> #taskSeq<'U>) -> taskSeq: taskSeq<'T> -> taskSeq<'U> - - /// Applies the given function to the items in the taskSeq and concatenates all the results in order. - val collectSeq: binder: ('T -> #seq<'U>) -> taskSeq: taskSeq<'T> -> taskSeq<'U> - - /// Applies the given async function to the items in the taskSeq and concatenates all the results in order. - val collectAsync: binder: ('T -> #Task<'TSeqU>) -> taskSeq: taskSeq<'T> -> taskSeq<'U> when 'TSeqU :> taskSeq<'U> - - /// Applies the given async function to the items in the taskSeq and concatenates all the results in order. - val collectSeqAsync: binder: ('T -> #Task<'SeqU>) -> taskSeq: taskSeq<'T> -> taskSeq<'U> when 'SeqU :> seq<'U> - - /// - /// Returns the first element of the , or if the sequence is empty. - /// - /// Thrown when the sequence is empty. - val tryHead: taskSeq: taskSeq<'T> -> Task<'T option> - - /// - /// Returns the first element of the . - /// - /// Thrown when the sequence is empty. - val head: taskSeq: taskSeq<'T> -> Task<'T> - - /// - /// Returns the last element of the , or if the sequence is empty. - /// - /// Thrown when the sequence is empty. - val tryLast: taskSeq: taskSeq<'T> -> Task<'T option> - - /// - /// Returns the last element of the . - /// - /// Thrown when the sequence is empty. - val last: taskSeq: taskSeq<'T> -> Task<'T> - - /// - /// Returns the nth element of the , or if the sequence - /// does not contain enough elements, or if is negative. - /// Parameter is zero-based, that is, the value 0 returns the first element. - /// - val tryItem: index: int -> taskSeq: taskSeq<'T> -> Task<'T option> - - /// - /// Returns the nth element of the , or if the sequence - /// does not contain enough elements, or if is negative. - /// - /// Thrown when the sequence has insufficient length or - /// is negative. - val item: index: int -> taskSeq: taskSeq<'T> -> Task<'T> - - /// - /// Returns the only element of the task sequence, or if the sequence is empty of - /// contains more than one element. - /// - val tryExactlyOne: source: taskSeq<'T> -> Task<'T option> - - /// - /// Returns the only element of the task sequence. - /// - /// Thrown when the input sequence does not contain precisely one element. - val exactlyOne: source: taskSeq<'T> -> Task<'T> - - /// - /// Applies the given function to each element of the task sequence. Returns - /// a sequence comprised of the results "x" for each element where - /// the function returns Some(x). - /// If is asynchronous, consider using . - /// + val append: source1: #taskSeq<'T> -> source2: #taskSeq<'T> -> taskSeq<'T> + val appendSeq: source1: #taskSeq<'T> -> source2: #seq<'T> -> taskSeq<'T> + val box: source: taskSeq<'T> -> taskSeq + val cast: source: taskSeq -> taskSeq<'T> val choose: chooser: ('T -> 'U option) -> source: taskSeq<'T> -> taskSeq<'U> - - /// - /// Applies the given asynchronous function to each element of the task sequence. Returns - /// a sequence comprised of the results "x" for each element where - /// the function returns . - /// If does not need to be asynchronous, consider using . - /// val chooseAsync: chooser: ('T -> #Task<'U option>) -> source: taskSeq<'T> -> taskSeq<'U> - - /// - /// Returns a new collection containing only the elements of the collection - /// for which the given function returns . - /// If is asynchronous, consider using . - /// + val collect: binder: ('T -> #taskSeq<'U>) -> source: taskSeq<'T> -> taskSeq<'U> + val collectAsync: binder: ('T -> #Task<'TSeqU>) -> source: taskSeq<'T> -> taskSeq<'U> when 'TSeqU :> taskSeq<'U> + val collectSeq: binder: ('T -> #seq<'U>) -> source: taskSeq<'T> -> taskSeq<'U> + val collectSeqAsync: binder: ('T -> #Task<'SeqU>) -> source: taskSeq<'T> -> taskSeq<'U> when 'SeqU :> seq<'U> + val concat: sources: taskSeq<#taskSeq<'T>> -> taskSeq<'T> + val contains<'T when 'T: equality> : value: 'T -> source: taskSeq<'T> -> Task + val delay: generator: (unit -> taskSeq<'T>) -> taskSeq<'T> + val empty<'T> : taskSeq<'T> + val exactlyOne: source: taskSeq<'T> -> Task<'T> + val except<'T when 'T: equality> : itemsToExclude: taskSeq<'T> -> source: taskSeq<'T> -> taskSeq<'T> + val exceptOfSeq<'T when 'T: equality> : itemsToExclude: seq<'T> -> source: taskSeq<'T> -> taskSeq<'T> + val exists: predicate: ('T -> bool) -> source: taskSeq<'T> -> Task + val existsAsync: predicate: ('T -> #Task) -> source: taskSeq<'T> -> Task val filter: predicate: ('T -> bool) -> source: taskSeq<'T> -> taskSeq<'T> - - /// - /// Returns a new collection containing only the elements of the collection - /// for which the given asynchronous function returns . - /// If does not need to be asynchronous, consider using . - /// val filterAsync: predicate: ('T -> #Task) -> source: taskSeq<'T> -> taskSeq<'T> - - /// - /// Applies the given function to successive elements of the task sequence - /// in , returning the first result where the function returns . - /// If is asynchronous, consider using . - /// - val tryPick: chooser: ('T -> 'U option) -> source: taskSeq<'T> -> Task<'U option> - - /// - /// Applies the given asynchronous function to successive elements of the task sequence - /// in , returning the first result where the function returns . - /// If does not need to be asynchronous, consider using . - /// - val tryPickAsync: chooser: ('T -> #Task<'U option>) -> source: taskSeq<'T> -> Task<'U option> - - /// - /// Returns the first element of the task sequence in for which the given function - /// returns . Returns if no such element exists. - /// If is asynchronous, consider using . - /// - val tryFind: predicate: ('T -> bool) -> source: taskSeq<'T> -> Task<'T option> - - /// - /// Returns the first element of the task sequence in for which the given asynchronous function - /// returns . Returns if no such element exists. - /// If does not need to be asynchronous, consider using . - /// - val tryFindAsync: predicate: ('T -> #Task) -> source: taskSeq<'T> -> Task<'T option> - - - /// - /// Applies the given function to successive elements of the task sequence - /// in , returning the first result where the function returns . - /// If is asynchronous, consider using . - /// Thrown when every item of the sequence - /// evaluates to when the given function is applied. - /// - val pick: chooser: ('T -> 'U option) -> source: taskSeq<'T> -> Task<'U> - - /// - /// Applies the given asynchronous function to successive elements of the task sequence - /// in , returning the first result where the function returns . - /// If does not need to be asynchronous, consider using . - /// Thrown when every item of the sequence - /// evaluates to when the given function is applied. - /// - val pickAsync: chooser: ('T -> #Task<'U option>) -> source: taskSeq<'T> -> Task<'U> - - /// - /// Returns the first element of the task sequence in for which the given function - /// returns . - /// If is asynchronous, consider using . - /// - /// Thrown if no element returns when - /// evaluated by the function. val find: predicate: ('T -> bool) -> source: taskSeq<'T> -> Task<'T> - - /// - /// Returns the first element of the task sequence in for which the given - /// asynchronous function returns . - /// If does not need to be asynchronous, consider using . - /// - /// Thrown if no element returns when - /// evaluated by the function. val findAsync: predicate: ('T -> #Task) -> source: taskSeq<'T> -> Task<'T> - - /// - /// Zips two task sequences, returning a taskSeq of the tuples of each sequence, in order. May raise ArgumentException - /// if the sequences are or unequal length. - /// - /// The sequences have different lengths. - val zip: taskSeq1: taskSeq<'T> -> taskSeq2: taskSeq<'U> -> IAsyncEnumerable<'T * 'U> - - /// - /// Applies the function to each element in the task sequence, - /// threading an accumulator argument of type through the computation. - /// If the accumulator function is asynchronous, consider using . - /// - val fold: folder: ('State -> 'T -> 'State) -> state: 'State -> taskSeq: taskSeq<'T> -> Task<'State> - - /// - /// Applies the asynchronous function to each element in the task sequence, - /// threading an accumulator argument of type through the computation. - /// If the accumulator function does not need to be asynchronous, consider using . - /// - val foldAsync: folder: ('State -> 'T -> #Task<'State>) -> state: 'State -> taskSeq: taskSeq<'T> -> Task<'State> - + val findIndex: predicate: ('T -> bool) -> source: taskSeq<'T> -> Task + val findIndexAsync: predicate: ('T -> #Task) -> source: taskSeq<'T> -> Task + val fold: folder: ('State -> 'T -> 'State) -> state: 'State -> source: taskSeq<'T> -> Task<'State> + val foldAsync: folder: ('State -> 'T -> #Task<'State>) -> state: 'State -> source: taskSeq<'T> -> Task<'State> + val head: source: taskSeq<'T> -> Task<'T> + val indexed: source: taskSeq<'T> -> taskSeq + val init: count: int -> initializer: (int -> 'T) -> taskSeq<'T> + val initAsync: count: int -> initializer: (int -> #Task<'T>) -> taskSeq<'T> + val initInfinite: initializer: (int -> 'T) -> taskSeq<'T> + val initInfiniteAsync: initializer: (int -> #Task<'T>) -> taskSeq<'T> + val isEmpty: source: taskSeq<'T> -> Task + val item: index: int -> source: taskSeq<'T> -> Task<'T> + val iter: action: ('T -> unit) -> source: taskSeq<'T> -> Task + val iterAsync: action: ('T -> #Task) -> source: taskSeq<'T> -> Task + val iteri: action: (int -> 'T -> unit) -> source: taskSeq<'T> -> Task + val iteriAsync: action: (int -> 'T -> #Task) -> source: taskSeq<'T> -> Task + val last: source: taskSeq<'T> -> Task<'T> + val length: source: taskSeq<'T> -> Task + val lengthBy: predicate: ('T -> bool) -> source: taskSeq<'T> -> Task + val lengthByAsync: predicate: ('T -> #Task) -> source: taskSeq<'T> -> Task + val lengthOrMax: max: int -> source: taskSeq<'T> -> Task + val map: mapper: ('T -> 'U) -> source: taskSeq<'T> -> taskSeq<'U> + val mapAsync: mapper: ('T -> #Task<'U>) -> source: taskSeq<'T> -> taskSeq<'U> + val mapi: mapper: (int -> 'T -> 'U) -> source: taskSeq<'T> -> taskSeq<'U> + val mapiAsync: mapper: (int -> 'T -> #Task<'U>) -> source: taskSeq<'T> -> taskSeq<'U> + val ofArray: source: 'T[] -> taskSeq<'T> + val ofAsyncArray: source: Async<'T> array -> taskSeq<'T> + val ofAsyncList: source: Async<'T> list -> taskSeq<'T> + val ofAsyncSeq: source: seq> -> taskSeq<'T> + val ofList: source: 'T list -> taskSeq<'T> + val ofResizeArray: source: ResizeArray<'T> -> taskSeq<'T> + val ofSeq: source: seq<'T> -> taskSeq<'T> + val ofTaskArray: source: #Task<'T> array -> taskSeq<'T> + val ofTaskList: source: #Task<'T> list -> taskSeq<'T> + val ofTaskSeq: source: seq<#Task<'T>> -> taskSeq<'T> + val pick: chooser: ('T -> 'U option) -> source: taskSeq<'T> -> Task<'U> + val pickAsync: chooser: ('T -> #Task<'U option>) -> source: taskSeq<'T> -> Task<'U> + val prependSeq: source1: #seq<'T> -> source2: #taskSeq<'T> -> taskSeq<'T> + val tail: source: taskSeq<'T> -> Task> + val toArray: source: taskSeq<'T> -> 'T[] + val toArrayAsync: source: taskSeq<'T> -> Task<'T[]> + val toIListAsync: source: taskSeq<'T> -> Task> + val toList: source: taskSeq<'T> -> 'T list + val toListAsync: source: taskSeq<'T> -> Task<'T list> + val toResizeArrayAsync: source: taskSeq<'T> -> Task> + val toSeq: source: taskSeq<'T> -> seq<'T> + val tryExactlyOne: source: taskSeq<'T> -> Task<'T option> + val tryFind: predicate: ('T -> bool) -> source: taskSeq<'T> -> Task<'T option> + val tryFindAsync: predicate: ('T -> #Task) -> source: taskSeq<'T> -> Task<'T option> + val tryFindIndex: predicate: ('T -> bool) -> source: taskSeq<'T> -> Task + val tryFindIndexAsync: predicate: ('T -> #Task) -> source: taskSeq<'T> -> Task + val tryHead: source: taskSeq<'T> -> Task<'T option> + val tryItem: index: int -> source: taskSeq<'T> -> Task<'T option> + val tryLast: source: taskSeq<'T> -> Task<'T option> + val tryPick: chooser: ('T -> 'U option) -> source: taskSeq<'T> -> Task<'U option> + val tryPickAsync: chooser: ('T -> #Task<'U option>) -> source: taskSeq<'T> -> Task<'U option> + val tryTail: source: taskSeq<'T> -> Task option> + val unbox<'U when 'U: struct> : source: taskSeq -> taskSeq<'U> + val zip: source1: taskSeq<'T> -> source2: taskSeq<'U> -> taskSeq<'T * 'U> ``` [buildstatus]: https://github.com/abelbraaksma/TaskSeq/actions/workflows/main.yaml From 404df4f15d27e6582bad0465db1b20f304d5ac97 Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Sun, 13 Nov 2022 02:59:20 +0100 Subject: [PATCH 04/68] Update package tags, and include symbols --- src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj b/src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj index 4287c5e9..d0298601 100644 --- a/src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj +++ b/src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj @@ -8,7 +8,7 @@ Computation expression 'taskSeq' for processing IAsyncEnumerable sequences and module functions $(Version) Abel Braaksma; Don Syme - This library brings C#'s concept of 'await foreach' to F#, with a seamless implementation of IAsyncEnumerable<'T>. + This library brings C#'s concept of 'await foreach' to F#, with a seamless implementation of IAsyncEnumerable<'T>. The 'taskSeq' computation expression adds support for awaitable asyncronous sequences with a similar ease of use and performance as F#'s 'task' CE, with minimal overhead through ValueTask under the hood. TaskSeq brings 'seq' and 'task' together in a safe way. @@ -50,6 +50,9 @@ Generates optimized IL code through the new resumable state machines, and comes - adds zip/length + taskseq'fsharp;f#;computation expression;IAsyncEnumerable;task;async;asyncseq; + True + snupkg From 8171fa5d8e87eb981f6f35c9cefa830930b6733c Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Sun, 13 Nov 2022 13:37:05 +0100 Subject: [PATCH 05/68] Add publish-to-nuget action on 'main', only when version changes --- .github/workflows/build.yaml | 20 -------------------- .github/workflows/main.yaml | 20 -------------------- .github/workflows/publish.yaml | 30 ++++++++++++++++++++++++++++++ .gitignore | 1 + src/FSharp.Control.TaskSeq.sln | 1 + 5 files changed, 32 insertions(+), 40 deletions(-) create mode 100644 .github/workflows/publish.yaml diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 4ac8d90b..19b688b1 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -18,23 +18,3 @@ jobs: # build it, test it, pack it - name: Run dotnet build (release) run: ./build.cmd - - # deploy: - # name: deploy - # runs-on: ubuntu-latest - # if: github.ref == 'refs/heads/main' - # steps: - # # checkout the code - # - name: checkout-code - # uses: actions/checkout@v3 - # with: - # fetch-depth: 0 - # # setup dotnet based on global.json - # - name: setup-dotnet - # uses: actions/setup-dotnet@v3 - # # push it to nuget - # - name: deploy - # run: make cd - # env: - # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 234f2b0b..02eb3c0d 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -45,23 +45,3 @@ jobs: # this path glob pattern requires forward slashes! path: ./src/FSharp.Control.TaskSeq.Test/TestResults/test-results-release.trx reporter: dotnet-trx - - # deploy: - # name: deploy - # runs-on: ubuntu-latest - # if: github.ref == 'refs/heads/main' - # steps: - # # checkout the code - # - name: checkout-code - # uses: actions/checkout@v3 - # with: - # fetch-depth: 0 - # # setup dotnet based on global.json - # - name: setup-dotnet - # uses: actions/setup-dotnet@v3 - # # push it to nuget - # - name: deploy - # run: make cd - # env: - # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml new file mode 100644 index 00000000..c97c3343 --- /dev/null +++ b/.github/workflows/publish.yaml @@ -0,0 +1,30 @@ +name: Pack & Publish Nuget + +on: + push: + branches: + - main + +jobs: + publish: + name: Publish nuget (if new version) + runs-on: windows-latest + steps: + # checkout the code + - name: checkout-code + uses: actions/checkout@v3 + with: + fetch-depth: 0 + # setup dotnet based on global.json + - name: setup-dotnet + uses: actions/setup-dotnet@v3 + # build it, test it, pack it, publish it + - name: Run dotnet build (release, for nuget) + run: ./build.cmd + - name: Nuget publish + # skip-duplicate ensures that the 409 error received when the package was already published, + # will just issue a warning and won't have the GH action fail. + # NUGET_PUBLISH_TOKEN_TASKSEQ is valid until approx. 8 Nov 2023 and will need to be updated by then. + # do so under https://github.com/fsprojects/FSharp.Control.TaskSeq/settings/secrets/actions + # select button "Add repository secret" or update the existing one under "Repository secrets" + run: dotnet nuget push packages\FSharp.Control.TaskSeq.*.nupkg --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NUGET_PUBLISH_TOKEN_TASKSEQ }} --skip-duplicate diff --git a/.gitignore b/.gitignore index f5910e87..72d94d66 100644 --- a/.gitignore +++ b/.gitignore @@ -349,3 +349,4 @@ MigrationBackup/ # Ionide (cross platform F# VS Code tools) working folder .ionide/ *.ncrunchproject +nuget-api-key.txt diff --git a/src/FSharp.Control.TaskSeq.sln b/src/FSharp.Control.TaskSeq.sln index ab1a7527..2e1e32f6 100644 --- a/src/FSharp.Control.TaskSeq.sln +++ b/src/FSharp.Control.TaskSeq.sln @@ -20,6 +20,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{ ..\.github\workflows\build.yaml = ..\.github\workflows\build.yaml ..\.github\dependabot.yml = ..\.github\dependabot.yml ..\.github\workflows\main.yaml = ..\.github\workflows\main.yaml + ..\.github\workflows\publish.yaml = ..\.github\workflows\publish.yaml ..\.github\workflows\test.yaml = ..\.github\workflows\test.yaml EndProjectSection EndProject From 2494e4661543951655d1ef042e253f91aee8c691 Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Mon, 14 Nov 2022 03:21:38 +0100 Subject: [PATCH 06/68] update preambule --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 126493b2..ca208da5 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,9 @@ # TaskSeq -An implementation of [`IAsyncEnumerable<'T>`][3] as a computation expression: `taskSeq { ... }` with an accompanying `TaskSeq` module. +An implementation of [`IAsyncEnumerable<'T>`][3] as a computation expression: `taskSeq { ... }` with an accompanying `TaskSeq` module, that allows seamless use of asynchronous sequences similar to F#'s native `seq` and `task` CE's. + +Latest version [can be installed from Nuget][nuget]. ----------------------------------------- @@ -532,4 +534,5 @@ module TaskSeq = [#82]: https://github.com/fsprojects/FSharp.Control.TaskSeq/pull/82 [#83]: https://github.com/fsprojects/FSharp.Control.TaskSeq/pull/83 -[issues]: https://github.com/fsprojects/FSharp.Control.TaskSeq/issues \ No newline at end of file +[issues]: https://github.com/fsprojects/FSharp.Control.TaskSeq/issues +[nuget]: https://www.nuget.org/packages/FSharp.Control.TaskSeq/ From 99fe9604e18b747b4e94b058e807f15696f2c7bc Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Mon, 14 Nov 2022 03:12:17 +0100 Subject: [PATCH 07/68] Fix permissions for running test reports --- .github/workflows/test.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 5d5042af..99a7993e 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -2,6 +2,11 @@ name: ci-test on: [pull_request] +permissions: + id-token: write + contents: read + checks: write + jobs: test-release: name: Test Release Build From 7860a087d8bdd07541efec913a074439b569005d Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Thu, 24 Nov 2022 16:31:02 +0100 Subject: [PATCH 08/68] Remove `invalidArg` exception from `TaskSeq.zip` --- .../TaskSeq.Zip.Tests.fs | 29 ------------------- .../FSharp.Control.TaskSeq.fsproj | 2 ++ src/FSharp.Control.TaskSeq/TaskSeqInternal.fs | 11 ------- 3 files changed, 2 insertions(+), 40 deletions(-) diff --git a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Zip.Tests.fs b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Zip.Tests.fs index 2d0b2f12..1206d74c 100644 --- a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Zip.Tests.fs +++ b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Zip.Tests.fs @@ -132,32 +132,3 @@ module Other = combined |> should equal [| ("one", 42L); ("two", 43L) |] } - - [] - let ``TaskSeq-zip throws on unequal lengths, variant`` leftThrows = task { - let long = Gen.sideEffectTaskSeq 11 - let short = Gen.sideEffectTaskSeq 10 - - let combined = - if leftThrows then - TaskSeq.zip short long - else - TaskSeq.zip long short - - fun () -> TaskSeq.toArrayAsync combined |> Task.ignore - |> should throwAsyncExact typeof - } - - [] - let ``TaskSeq-zip throws on unequal lengths with empty seq`` leftThrows = task { - let one = Gen.sideEffectTaskSeq 1 - - let combined = - if leftThrows then - TaskSeq.zip TaskSeq.empty one - else - TaskSeq.zip one TaskSeq.empty - - fun () -> TaskSeq.toArrayAsync combined |> Task.ignore - |> should throwAsyncExact typeof - } diff --git a/src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj b/src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj index d0298601..260225ff 100644 --- a/src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj +++ b/src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj @@ -23,6 +23,8 @@ Generates optimized IL code through the new resumable state machines, and comes nuget-package-readme.md Release notes: + 0.2.3 + - do not throw exception for unequal lengths in TaskSeq.zip, fixes #32 0.2.2 - removes TaskSeq.toSeqCachedAsync, which was incorrectly named. Use toSeq or toListAsync instead. - renames TaskSeq.toSeqCached to TaskSeq.toSeq, which was its actual operational behavior. diff --git a/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs b/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs index 7779429a..a6499178 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs +++ b/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs @@ -272,28 +272,17 @@ module internal TaskSeqInternal = } let zip (source1: taskSeq<_>) (source2: taskSeq<_>) = taskSeq { - let inline validate step1 step2 = - if step1 <> step2 then - if step1 then - invalidArg "taskSequence1" "The task sequences have different lengths." - - if step2 then - invalidArg "taskSequence2" "The task sequences have different lengths." - - use e1 = source1.GetAsyncEnumerator(CancellationToken()) use e2 = source2.GetAsyncEnumerator(CancellationToken()) let mutable go = true let! step1 = e1.MoveNextAsync() let! step2 = e2.MoveNextAsync() go <- step1 && step2 - validate step1 step2 while go do yield e1.Current, e2.Current let! step1 = e1.MoveNextAsync() let! step2 = e2.MoveNextAsync() - validate step1 step2 go <- step1 && step2 } From 04d11475ef1fa1a23ddb0460ddef9c478ee95a3f Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Thu, 24 Nov 2022 16:20:57 +0100 Subject: [PATCH 09/68] Attempt to fix permissions --- .github/workflows/test.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 99a7993e..2ab1e0ce 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -9,6 +9,7 @@ permissions: jobs: test-release: + permissions: write-all name: Test Release Build runs-on: windows-latest steps: @@ -33,6 +34,7 @@ jobs: reporter: dotnet-trx test-debug: + permissions: write-all name: Test Debug Build runs-on: windows-latest steps: From ee50c85d4056b9b5ba53d1c6b6745f27b0583cfb Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Thu, 24 Nov 2022 17:02:11 +0100 Subject: [PATCH 10/68] Use separate CI workflow to overcome issue with Dorny --- .github/workflows/test-report.yaml | 31 ++++++++++++++++++++++++++++++ .github/workflows/test.yaml | 24 +++++++---------------- src/FSharp.Control.TaskSeq.sln | 1 + 3 files changed, 39 insertions(+), 17 deletions(-) create mode 100644 .github/workflows/test-report.yaml diff --git a/.github/workflows/test-report.yaml b/.github/workflows/test-report.yaml new file mode 100644 index 00000000..18f70b06 --- /dev/null +++ b/.github/workflows/test-report.yaml @@ -0,0 +1,31 @@ +name: 'Test Report' + +# See Dorny instructions for why we need a separate yaml for creating a test report +# for public repositories that accept forks: +# https://github.com/dorny/test-reporter#recommended-setup-for-public-repositories + +on: + workflow_run: + workflows: ['ci-test'] # runs after CI workflow + types: + - completed +jobs: + test-report-release: + runs-on: ubuntu-latest + steps: + - uses: dorny/test-reporter@v1 + with: + artifact: test-results-release # artifact name + name: Report release tests # Name of the check run which will be created + path: '**/*.trx' # Path to test results (inside artifact .zip) + reporter: dotnet-trx # Format of test results + + test-report-debug: + runs-on: ubuntu-latest + steps: + - uses: dorny/test-reporter@v1 + with: + artifact: test-results-debug # artifact name + name: Report debug tests # Name of the check run which will be created + path: '**/*.trx' # Path to test results (inside artifact .zip) + reporter: dotnet-trx # Format of test results diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 2ab1e0ce..1fb0eed6 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -2,14 +2,8 @@ name: ci-test on: [pull_request] -permissions: - id-token: write - contents: read - checks: write - jobs: test-release: - permissions: write-all name: Test Release Build runs-on: windows-latest steps: @@ -24,17 +18,15 @@ jobs: # build it, test it - name: Run dotnet test - release run: ./build.cmd ci -release - - name: Publish test results - release - uses: dorny/test-reporter@v1 - if: always() + - uses: actions/upload-artifact@v2 # upload test results + if: success() || failure() # run this step even if previous step failed with: - name: Report release tests + name: test-results-release # this path glob pattern requires forward slashes! path: ./src/FSharp.Control.TaskSeq.Test/TestResults/test-results-release.trx - reporter: dotnet-trx + test-debug: - permissions: write-all name: Test Debug Build runs-on: windows-latest steps: @@ -49,11 +41,9 @@ jobs: # build it, test it - name: Run dotnet test - debug run: ./build.cmd ci -debug - - name: Publish test results - debug - uses: dorny/test-reporter@v1 - if: always() + - uses: actions/upload-artifact@v2 # upload test results + if: success() || failure() # run this step even if previous step failed with: - name: Report debug tests + name: test-results-debug # this path glob pattern requires forward slashes! path: ./src/FSharp.Control.TaskSeq.Test/TestResults/test-results-debug.trx - reporter: dotnet-trx diff --git a/src/FSharp.Control.TaskSeq.sln b/src/FSharp.Control.TaskSeq.sln index 2e1e32f6..f3413592 100644 --- a/src/FSharp.Control.TaskSeq.sln +++ b/src/FSharp.Control.TaskSeq.sln @@ -21,6 +21,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{ ..\.github\dependabot.yml = ..\.github\dependabot.yml ..\.github\workflows\main.yaml = ..\.github\workflows\main.yaml ..\.github\workflows\publish.yaml = ..\.github\workflows\publish.yaml + ..\.github\workflows\test-report.yaml = ..\.github\workflows\test-report.yaml ..\.github\workflows\test.yaml = ..\.github\workflows\test.yaml EndProjectSection EndProject From 40db1a028b61e8cff4b1a5bc1ea6f972fefd22c5 Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Thu, 24 Nov 2022 17:36:35 +0100 Subject: [PATCH 11/68] Retry creating test report with Dorny --- .github/workflows/test-report.yaml | 18 ++++++++++-------- .github/workflows/test.yaml | 16 ++++++++++++---- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/.github/workflows/test-report.yaml b/.github/workflows/test-report.yaml index 18f70b06..82549238 100644 --- a/.github/workflows/test-report.yaml +++ b/.github/workflows/test-report.yaml @@ -1,4 +1,4 @@ -name: 'Test Report' +name: ci-report # See Dorny instructions for why we need a separate yaml for creating a test report # for public repositories that accept forks: @@ -6,26 +6,28 @@ name: 'Test Report' on: workflow_run: - workflows: ['ci-test'] # runs after CI workflow + workflows: ci-test # runs after CI workflow types: - completed jobs: test-report-release: - runs-on: ubuntu-latest + runs-on: windows-latest + name: Test report (release) steps: - uses: dorny/test-reporter@v1 with: artifact: test-results-release # artifact name - name: Report release tests # Name of the check run which will be created - path: '**/*.trx' # Path to test results (inside artifact .zip) + name: Dorny release tests # Name of the check run which will be created + path: '*.trx' # Path to test results (inside artifact .zip) reporter: dotnet-trx # Format of test results test-report-debug: - runs-on: ubuntu-latest + runs-on: windows-latest + name: Test report (debug) steps: - uses: dorny/test-reporter@v1 with: artifact: test-results-debug # artifact name - name: Report debug tests # Name of the check run which will be created - path: '**/*.trx' # Path to test results (inside artifact .zip) + name: Dorny debug tests # Name of the check run which will be created + path: '*.trx' # Path to test results (inside artifact .zip) reporter: dotnet-trx # Format of test results diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 1fb0eed6..41a854eb 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -12,14 +12,18 @@ jobs: uses: actions/checkout@v3 with: fetch-depth: 0 + # setup dotnet based on global.json - name: setup-dotnet uses: actions/setup-dotnet@v3 + # build it, test it - name: Run dotnet test - release run: ./build.cmd ci -release - - uses: actions/upload-artifact@v2 # upload test results - if: success() || failure() # run this step even if previous step failed + + # upload test results + - uses: actions/upload-artifact@v2 + if: success() || failure() with: name: test-results-release # this path glob pattern requires forward slashes! @@ -35,14 +39,18 @@ jobs: uses: actions/checkout@v3 with: fetch-depth: 0 + # setup dotnet based on global.json - name: setup-dotnet uses: actions/setup-dotnet@v3 + # build it, test it - name: Run dotnet test - debug run: ./build.cmd ci -debug - - uses: actions/upload-artifact@v2 # upload test results - if: success() || failure() # run this step even if previous step failed + + # upload test results + - uses: actions/upload-artifact@v2 + if: success() || failure() with: name: test-results-debug # this path glob pattern requires forward slashes! From 2d9f73413e367f0510f7e373b25e498f535ff355 Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Thu, 24 Nov 2022 18:04:47 +0100 Subject: [PATCH 12/68] Change CI step names for reports --- .github/workflows/test-report.yaml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test-report.yaml b/.github/workflows/test-report.yaml index 82549238..7edd203d 100644 --- a/.github/workflows/test-report.yaml +++ b/.github/workflows/test-report.yaml @@ -6,28 +6,26 @@ name: ci-report on: workflow_run: - workflows: ci-test # runs after CI workflow + workflows: ['ci-test'] # runs after CI workflow types: - completed jobs: test-report-release: runs-on: windows-latest - name: Test report (release) steps: - uses: dorny/test-reporter@v1 with: artifact: test-results-release # artifact name - name: Dorny release tests # Name of the check run which will be created + name: Report release tests # Name of the check run which will be created path: '*.trx' # Path to test results (inside artifact .zip) reporter: dotnet-trx # Format of test results test-report-debug: runs-on: windows-latest - name: Test report (debug) steps: - uses: dorny/test-reporter@v1 with: artifact: test-results-debug # artifact name - name: Dorny debug tests # Name of the check run which will be created + name: Report debug tests # Name of the check run which will be created path: '*.trx' # Path to test results (inside artifact .zip) reporter: dotnet-trx # Format of test results From bd8af182b87a3083883485bd6a54977883855b4e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Nov 2022 14:13:54 +0000 Subject: [PATCH 13/68] Bump FsToolkit.ErrorHandling.TaskResult from 3.2.0 to 4.0.0 Bumps [FsToolkit.ErrorHandling.TaskResult](https://github.com/demystifyfp/FsToolkit.ErrorHandling) from 3.2.0 to 4.0.0. - [Release notes](https://github.com/demystifyfp/FsToolkit.ErrorHandling/releases) - [Changelog](https://github.com/demystifyfp/FsToolkit.ErrorHandling/blob/master/RELEASE_NOTES.md) - [Commits](https://github.com/demystifyfp/FsToolkit.ErrorHandling/compare/3.2.0...4.0.0) --- updated-dependencies: - dependency-name: FsToolkit.ErrorHandling.TaskResult dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .../FSharp.Control.TaskSeq.Test.fsproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj b/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj index a0760a87..0d0b6019 100644 --- a/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj +++ b/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj @@ -54,7 +54,7 @@ - + From 66daab4ab60d933b4716fc396528c8c5ce288db3 Mon Sep 17 00:00:00 2001 From: Gustavo Leon <1261319+gusty@users.noreply.github.com> Date: Sun, 13 Nov 2022 09:32:57 +0100 Subject: [PATCH 14/68] Rewrite empty without CE --- src/FSharp.Control.TaskSeq/TaskSeq.fs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/FSharp.Control.TaskSeq/TaskSeq.fs b/src/FSharp.Control.TaskSeq/TaskSeq.fs index b94fbb26..d6cb2a30 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeq.fs +++ b/src/FSharp.Control.TaskSeq/TaskSeq.fs @@ -11,10 +11,15 @@ module TaskSeq = // Just for convenience module Internal = TaskSeqInternal - let empty<'T> = taskSeq { - for c: 'T in [] do - yield c - } + let empty<'T> = + { new IAsyncEnumerable<'T> with + member _.GetAsyncEnumerator (_) = + { new IAsyncEnumerator<'T> with + member _.MoveNextAsync () = ValueTask.FromResult false + member _.get_Current () = invalidOp "The sequence is empty." + member _.DisposeAsync () = ValueTask.CompletedTask + } + } let isEmpty source = Internal.isEmpty source From 0ce42b60b00f9ac020a2ed8a82acda29b97c3bdc Mon Sep 17 00:00:00 2001 From: Gustavo Leon <1261319+gusty@users.noreply.github.com> Date: Sun, 13 Nov 2022 10:52:02 +0100 Subject: [PATCH 15/68] Align with tests expectations --- src/FSharp.Control.TaskSeq/TaskSeq.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FSharp.Control.TaskSeq/TaskSeq.fs b/src/FSharp.Control.TaskSeq/TaskSeq.fs index d6cb2a30..2d32b4bb 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeq.fs +++ b/src/FSharp.Control.TaskSeq/TaskSeq.fs @@ -16,7 +16,7 @@ module TaskSeq = member _.GetAsyncEnumerator (_) = { new IAsyncEnumerator<'T> with member _.MoveNextAsync () = ValueTask.FromResult false - member _.get_Current () = invalidOp "The sequence is empty." + member _.get_Current () = Unchecked.defaultof<'T> member _.DisposeAsync () = ValueTask.CompletedTask } } From 71240f74102f1f43593f47d6f67c305e6f2dbf5c Mon Sep 17 00:00:00 2001 From: Gustavo Leon <1261319+gusty@users.noreply.github.com> Date: Sun, 13 Nov 2022 18:57:32 +0100 Subject: [PATCH 16/68] Update src/FSharp.Control.TaskSeq/TaskSeq.fs Co-authored-by: Abel Braaksma --- src/FSharp.Control.TaskSeq/TaskSeq.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FSharp.Control.TaskSeq/TaskSeq.fs b/src/FSharp.Control.TaskSeq/TaskSeq.fs index 2d32b4bb..6e8dec95 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeq.fs +++ b/src/FSharp.Control.TaskSeq/TaskSeq.fs @@ -16,7 +16,7 @@ module TaskSeq = member _.GetAsyncEnumerator (_) = { new IAsyncEnumerator<'T> with member _.MoveNextAsync () = ValueTask.FromResult false - member _.get_Current () = Unchecked.defaultof<'T> + member _.Current with get() = Unchecked.defaultof<'T> member _.DisposeAsync () = ValueTask.CompletedTask } } From 21b07d18ea4b9f19d39290f82bb021f631061165 Mon Sep 17 00:00:00 2001 From: Gustavo Leon <1261319+gusty@users.noreply.github.com> Date: Sun, 13 Nov 2022 18:57:59 +0100 Subject: [PATCH 17/68] Apply format Co-authored-by: Abel Braaksma --- src/FSharp.Control.TaskSeq/TaskSeq.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FSharp.Control.TaskSeq/TaskSeq.fs b/src/FSharp.Control.TaskSeq/TaskSeq.fs index 6e8dec95..fc89fc2a 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeq.fs +++ b/src/FSharp.Control.TaskSeq/TaskSeq.fs @@ -17,7 +17,7 @@ module TaskSeq = { new IAsyncEnumerator<'T> with member _.MoveNextAsync () = ValueTask.FromResult false member _.Current with get() = Unchecked.defaultof<'T> - member _.DisposeAsync () = ValueTask.CompletedTask + member _.DisposeAsync () = ValueTask.CompletedTask } } From 2ca6c2118ce741fa5d41a734e9c481f302ec185e Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Thu, 24 Nov 2022 16:04:20 +0100 Subject: [PATCH 18/68] Move ValueTask constructors to more readable module extensions --- src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs | 11 ++++++----- src/FSharp.Control.TaskSeq/Utils.fs | 16 +++++++++++++++- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs b/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs index 8ec5cf34..2331fc28 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs +++ b/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs @@ -13,6 +13,7 @@ open System.Threading.Tasks.Sources open FSharp.Core.CompilerServices open FSharp.Core.CompilerServices.StateMachineHelpers +open FSharp.Control [] @@ -279,14 +280,14 @@ and [] TaskSeq<'Machine, 'T if this._machine.ResumptionPoint = -1 then // can't use as IAsyncEnumerator before IAsyncEnumerable logInfo "at MoveNextAsync: Resumption point = -1" - ValueTask() + ValueTask.False elif this._machine.Data.completed then logInfo "at MoveNextAsync: completed = true" // return False when beyond the last item this._machine.Data.promiseOfValueOrEnd.Reset() - ValueTask() + ValueTask.False else logInfo "at MoveNextAsync: normal resumption scenario" @@ -343,18 +344,18 @@ and [] TaskSeq<'Machine, 'T // the Current value data.current <- ValueNone - ValueTask(result) + ValueTask.FromResult result | ValueTaskSourceStatus.Faulted | ValueTaskSourceStatus.Canceled | ValueTaskSourceStatus.Pending as state -> logInfo ("at MoveNextAsyncResult: case ", state) - ValueTask(this, version) // uses IValueTaskSource<'T> + ValueTask.ofIValueTaskSource this version | _ -> logInfo "at MoveNextAsyncResult: Unexpected state" // assume it's a possibly new, not yet supported case, treat as default - ValueTask(this, version) // uses IValueTaskSource<'T> + ValueTask.ofIValueTaskSource this version and TaskSeqCode<'T> = ResumableCode, unit> and TaskSeqStateMachine<'T> = ResumableStateMachine> diff --git a/src/FSharp.Control.TaskSeq/Utils.fs b/src/FSharp.Control.TaskSeq/Utils.fs index 74bd115a..09d0e504 100644 --- a/src/FSharp.Control.TaskSeq/Utils.fs +++ b/src/FSharp.Control.TaskSeq/Utils.fs @@ -5,12 +5,26 @@ open System.Threading.Tasks [] module ValueTaskExtensions = /// Extensions for ValueTask that are not available in NetStandard 2.1, but are - /// available in .NET 5+. + /// available in .NET 5+. We put them in Extension space to mimic the behavior of NetStandard 2.1 type ValueTask with /// (Extension member) Gets a task that has already completed successfully. static member inline CompletedTask = Unchecked.defaultof + +module ValueTask = + /// A successfully completed ValueTask of boolean that has the value false. + let False = ValueTask() + + /// A successfully completed ValueTask of boolean that has the value true. + let True = ValueTask true + + /// Creates a ValueTask with the supplied result of the successful operation. + let inline FromResult (x: 'T) = ValueTask<'T> x + + /// Creates a ValueTask with an IValueTaskSource representing the operation + let inline ofIValueTaskSource taskSource version = ValueTask(taskSource, version) + module Task = /// Convert an Async<'T> into a Task<'T> let inline ofAsync (async: Async<'T>) = task { return! async } From c867ef6e84c2abdfeb9fcdecd07c9cc364532f70 Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Thu, 24 Nov 2022 16:04:46 +0100 Subject: [PATCH 19/68] Fix compile errors in TaskSeq.empty --- src/FSharp.Control.TaskSeq/TaskSeq.fs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/FSharp.Control.TaskSeq/TaskSeq.fs b/src/FSharp.Control.TaskSeq/TaskSeq.fs index fc89fc2a..8e4ae80c 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeq.fs +++ b/src/FSharp.Control.TaskSeq/TaskSeq.fs @@ -13,11 +13,11 @@ module TaskSeq = let empty<'T> = { new IAsyncEnumerable<'T> with - member _.GetAsyncEnumerator (_) = + member _.GetAsyncEnumerator(_) = { new IAsyncEnumerator<'T> with - member _.MoveNextAsync () = ValueTask.FromResult false - member _.Current with get() = Unchecked.defaultof<'T> - member _.DisposeAsync () = ValueTask.CompletedTask + member _.MoveNextAsync() = ValueTask.False + member _.Current = Unchecked.defaultof<'T> + member _.DisposeAsync() = ValueTask.CompletedTask } } From 0ee794b7ba4157dd8f92d8098e87cf17134ae9cb Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Thu, 24 Nov 2022 18:54:06 +0100 Subject: [PATCH 20/68] Update changelog --- src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj b/src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj index 260225ff..7c223944 100644 --- a/src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj +++ b/src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj @@ -24,6 +24,7 @@ Generates optimized IL code through the new resumable state machines, and comes Release notes: 0.2.3 + - improve TaskSeq.empty by not relying on resumable state, #89 - do not throw exception for unequal lengths in TaskSeq.zip, fixes #32 0.2.2 - removes TaskSeq.toSeqCachedAsync, which was incorrectly named. Use toSeq or toListAsync instead. From d43d6c6abc50fb0585e04986c9e0cb07be7d7a28 Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Thu, 24 Nov 2022 19:31:23 +0100 Subject: [PATCH 21/68] Downgrade FsToolkit so we can continue using 6.0.3 in our tests --- .../FSharp.Control.TaskSeq.Test.fsproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj b/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj index 0d0b6019..7eb542e8 100644 --- a/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj +++ b/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj @@ -1,4 +1,4 @@ - + net6.0 @@ -54,7 +54,7 @@ - + From 50205383d95c93c7225ddb3d7db638b105d9b8a1 Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Thu, 24 Nov 2022 22:07:49 +0100 Subject: [PATCH 22/68] Fix by using shell `cmd`, instead of default `pwsh`, which swallows `%errorlevel%` --- .github/workflows/build.yaml | 3 +++ .github/workflows/main.yaml | 6 ++++++ .github/workflows/publish.yaml | 2 ++ .github/workflows/test-report.yaml | 4 ++-- .github/workflows/test.yaml | 6 ++++++ 5 files changed, 19 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 19b688b1..6bf63d49 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -17,4 +17,7 @@ jobs: uses: actions/setup-dotnet@v3 # build it, test it, pack it - name: Run dotnet build (release) + # see issue #105 + # very important, since we use cmd scripts, the default is psh, and a bug prevents errorlevel to bubble + shell: cmd run: ./build.cmd diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 02eb3c0d..196dac9e 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -20,6 +20,9 @@ jobs: uses: actions/setup-dotnet@v3 # build it, test it, pack it - name: Run dotnet build (release) + # see issue #105 + # very important, since we use cmd scripts, the default is psh, and a bug prevents errorlevel to bubble + shell: cmd run: ./build.cmd test-release: @@ -36,6 +39,9 @@ jobs: uses: actions/setup-dotnet@v3 # build it, test it, pack it - name: Run dotnet test - release + # see issue #105 + # very important, since we use cmd scripts, the default is psh, and a bug prevents errorlevel to bubble + shell: cmd run: ./build.cmd ci -release - name: Publish test results - release uses: dorny/test-reporter@v1 diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index c97c3343..bff72a75 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -20,6 +20,8 @@ jobs: uses: actions/setup-dotnet@v3 # build it, test it, pack it, publish it - name: Run dotnet build (release, for nuget) + # see issue #105 + # very important, since we use cmd scripts, the default is psh, and a bug prevents errorlevel to bubble run: ./build.cmd - name: Nuget publish # skip-duplicate ensures that the 409 error received when the package was already published, diff --git a/.github/workflows/test-report.yaml b/.github/workflows/test-report.yaml index 7edd203d..1f472625 100644 --- a/.github/workflows/test-report.yaml +++ b/.github/workflows/test-report.yaml @@ -16,7 +16,7 @@ jobs: - uses: dorny/test-reporter@v1 with: artifact: test-results-release # artifact name - name: Report release tests # Name of the check run which will be created + name: Report release tests # Name of the check run which will be created path: '*.trx' # Path to test results (inside artifact .zip) reporter: dotnet-trx # Format of test results @@ -26,6 +26,6 @@ jobs: - uses: dorny/test-reporter@v1 with: artifact: test-results-debug # artifact name - name: Report debug tests # Name of the check run which will be created + name: Report debug tests # Name of the check run which will be created path: '*.trx' # Path to test results (inside artifact .zip) reporter: dotnet-trx # Format of test results diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 41a854eb..c71f1d54 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -19,6 +19,9 @@ jobs: # build it, test it - name: Run dotnet test - release + # see issue #105 + # very important, since we use cmd scripts, the default is psh, and a bug prevents errorlevel to bubble + shell: cmd run: ./build.cmd ci -release # upload test results @@ -46,6 +49,9 @@ jobs: # build it, test it - name: Run dotnet test - debug + # see issue #105 + # very important, since we use cmd scripts, the default is psh, and a bug prevents errorlevel to bubble + shell: cmd run: ./build.cmd ci -debug # upload test results From 764abc4354f63401a3cedfad8d13390100671cc9 Mon Sep 17 00:00:00 2001 From: Gustavo Leon <1261319+gusty@users.noreply.github.com> Date: Sun, 13 Nov 2022 09:56:39 +0100 Subject: [PATCH 23/68] + singleton --- src/FSharp.Control.TaskSeq/TaskSeq.fs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/FSharp.Control.TaskSeq/TaskSeq.fs b/src/FSharp.Control.TaskSeq/TaskSeq.fs index 8e4ae80c..87831249 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeq.fs +++ b/src/FSharp.Control.TaskSeq/TaskSeq.fs @@ -21,6 +21,20 @@ module TaskSeq = } } + let singleton (source: 'T) = + { new IAsyncEnumerable<'T> with + member _.GetAsyncEnumerator(_) = + let mutable started = false + { new IAsyncEnumerator<'T> with + member _.MoveNextAsync () = + let r = ValueTask.FromResult (not started) + started <- true + r + member _.get_Current () : 'T = if started then source else invalidOp "Enumeration has not started. Call MoveNextAsync." + member _.DisposeAsync () = ValueTask.CompletedTask + } + } + let isEmpty source = Internal.isEmpty source // From 5c40b77922a1a8740c7a203ce2e5ac2951033886 Mon Sep 17 00:00:00 2001 From: Gustavo Leon <1261319+gusty@users.noreply.github.com> Date: Sun, 13 Nov 2022 10:00:19 +0100 Subject: [PATCH 24/68] + fsi signature --- src/FSharp.Control.TaskSeq/TaskSeq.fsi | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/FSharp.Control.TaskSeq/TaskSeq.fsi b/src/FSharp.Control.TaskSeq/TaskSeq.fsi index aeae538d..054d3609 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeq.fsi +++ b/src/FSharp.Control.TaskSeq/TaskSeq.fsi @@ -7,6 +7,9 @@ module TaskSeq = /// Initialize an empty taskSeq. val empty<'T> : taskSeq<'T> + + /// Creates a taskSeq sequence that generates a single element and then ends. + val singleton : source: 'T -> taskSeq<'T> /// /// Returns if the task sequence contains no elements, otherwise. From 8f0d3e5eb297d498ed522025ec8630d51fe527e3 Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Thu, 24 Nov 2022 19:45:01 +0100 Subject: [PATCH 25/68] Move implementation of singleton to 'internals' file, update doc comment and remove exception --- src/FSharp.Control.TaskSeq/TaskSeq.fs | 14 +------------- src/FSharp.Control.TaskSeq/TaskSeq.fsi | 8 +++++--- src/FSharp.Control.TaskSeq/TaskSeqInternal.fs | 16 ++++++++++++++++ 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/src/FSharp.Control.TaskSeq/TaskSeq.fs b/src/FSharp.Control.TaskSeq/TaskSeq.fs index 87831249..0dbc0c00 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeq.fs +++ b/src/FSharp.Control.TaskSeq/TaskSeq.fs @@ -21,19 +21,7 @@ module TaskSeq = } } - let singleton (source: 'T) = - { new IAsyncEnumerable<'T> with - member _.GetAsyncEnumerator(_) = - let mutable started = false - { new IAsyncEnumerator<'T> with - member _.MoveNextAsync () = - let r = ValueTask.FromResult (not started) - started <- true - r - member _.get_Current () : 'T = if started then source else invalidOp "Enumeration has not started. Call MoveNextAsync." - member _.DisposeAsync () = ValueTask.CompletedTask - } - } + let singleton (source: 'T) = Internal.singleton source let isEmpty source = Internal.isEmpty source diff --git a/src/FSharp.Control.TaskSeq/TaskSeq.fsi b/src/FSharp.Control.TaskSeq/TaskSeq.fsi index 054d3609..0d9fe0c9 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeq.fsi +++ b/src/FSharp.Control.TaskSeq/TaskSeq.fsi @@ -7,9 +7,11 @@ module TaskSeq = /// Initialize an empty taskSeq. val empty<'T> : taskSeq<'T> - - /// Creates a taskSeq sequence that generates a single element and then ends. - val singleton : source: 'T -> taskSeq<'T> + + /// + /// Creates a sequence from that generates a single element and then ends. + /// + val singleton: source: 'T -> taskSeq<'T> /// /// Returns if the task sequence contains no elements, otherwise. diff --git a/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs b/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs index a6499178..636be049 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs +++ b/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs @@ -61,6 +61,22 @@ module internal TaskSeqInternal = return not step } + let singleton (source: 'T) = + { new IAsyncEnumerable<'T> with + member _.GetAsyncEnumerator(_) = + let mutable ended = false + + { new IAsyncEnumerator<'T> with + member _.MoveNextAsync() = + let vt = ValueTask.FromResult(not ended) + ended <- true + vt + + member _.Current: 'T = if ended then Unchecked.defaultof<'T> else source + member _.DisposeAsync() = ValueTask.CompletedTask + } + } + /// Returns length unconditionally, or based on a predicate let lengthBy predicate (source: taskSeq<_>) = task { use e = source.GetAsyncEnumerator(CancellationToken()) From 368be2016d55438881ab9e6d48bc607bbbe0fd6c Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Thu, 24 Nov 2022 19:46:47 +0100 Subject: [PATCH 26/68] Update release notes --- src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj b/src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj index 7c223944..ffe66c2f 100644 --- a/src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj +++ b/src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj @@ -23,8 +23,9 @@ Generates optimized IL code through the new resumable state machines, and comes nuget-package-readme.md Release notes: - 0.2.3 - - improve TaskSeq.empty by not relying on resumable state, #89 + 0.2.3 (unreleased) + - add TaskSeq.singleton, #90 (by @gusty) + - improve TaskSeq.empty by not relying on resumable state, #89 (by @gusty) - do not throw exception for unequal lengths in TaskSeq.zip, fixes #32 0.2.2 - removes TaskSeq.toSeqCachedAsync, which was incorrectly named. Use toSeq or toListAsync instead. From 8e8a16165a3af7c49652540b760e1b587bed18e7 Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Thu, 24 Nov 2022 19:47:58 +0100 Subject: [PATCH 27/68] Remove redundant code --- src/FSharp.Control.TaskSeq/TaskSeq.fs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/FSharp.Control.TaskSeq/TaskSeq.fs b/src/FSharp.Control.TaskSeq/TaskSeq.fs index 0dbc0c00..fb45a1ae 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeq.fs +++ b/src/FSharp.Control.TaskSeq/TaskSeq.fs @@ -39,10 +39,6 @@ module TaskSeq = e.DisposeAsync().AsTask().Wait() ] - let format x = string x - let f () = format 42 - - let toArray (source: taskSeq<'T>) = [| let e = source.GetAsyncEnumerator(CancellationToken()) From 5f8d8a2a9acc072b08d42c5a05c2c65e678699b8 Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Thu, 24 Nov 2022 22:28:27 +0100 Subject: [PATCH 28/68] Add tests for `TaskSeq.singleton` --- .../FSharp.Control.TaskSeq.Test.fsproj | 1 + .../TaskSeq.Singleton.Tests.fs | 80 +++++++++++++++++++ src/FSharp.Control.TaskSeq.Test/TestUtils.fs | 5 ++ 3 files changed, 86 insertions(+) create mode 100644 src/FSharp.Control.TaskSeq.Test/TaskSeq.Singleton.Tests.fs diff --git a/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj b/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj index 7eb542e8..fdeb70de 100644 --- a/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj +++ b/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj @@ -39,6 +39,7 @@ + diff --git a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Singleton.Tests.fs b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Singleton.Tests.fs new file mode 100644 index 00000000..ea2d2368 --- /dev/null +++ b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Singleton.Tests.fs @@ -0,0 +1,80 @@ +module TaskSeq.Tests.Singleton + +open System.Threading.Tasks +open Xunit +open FsUnit.Xunit +open FsToolkit.ErrorHandling + +open FSharp.Control + +module EmptySeq = + + [)>] + let ``TaskSeq-singleton with empty has length one`` variant = + taskSeq { + yield! TaskSeq.singleton 10 + yield! Gen.getEmptyVariant variant + } + |> TaskSeq.exactlyOne + |> Task.map (should equal 10) + +module Other = + [] + let ``TaskSeq-singleton creates a sequence of one`` () = + TaskSeq.singleton 42 + |> TaskSeq.exactlyOne + |> Task.map (should equal 42) + + [] + let ``TaskSeq-singleton can be yielded multiple times`` () = + let singleton = TaskSeq.singleton 42 + + taskSeq { + yield! singleton + yield! singleton + yield! singleton + yield! singleton + } + |> TaskSeq.toList + |> should equal [ 42; 42; 42; 42 ] + + [] + let ``TaskSeq-singleton with isEmpty`` () = + TaskSeq.singleton 42 + |> TaskSeq.isEmpty + |> Task.map (should be False) + + [] + let ``TaskSeq-singleton with append`` () = + TaskSeq.singleton 42 + |> TaskSeq.append (TaskSeq.singleton 42) + |> TaskSeq.toList + |> should equal [ 42; 42 ] + + [)>] + let ``TaskSeq-singleton with collect`` variant = + Gen.getSeqImmutable variant + |> TaskSeq.collect TaskSeq.singleton + |> verify1To10 + + [] + let ``TaskSeq-singleton does not throw when getting Current before MoveNext`` () = task { + let enumerator = (TaskSeq.singleton 42).GetAsyncEnumerator() + let defaultValue = enumerator.Current // should return the default value for int + defaultValue |> should equal 0 + } + + [] + let ``TaskSeq-singleton does not throw when getting Current after last MoveNext`` () = task { + let enumerator = (TaskSeq.singleton 42).GetAsyncEnumerator() + let! isNext = enumerator.MoveNextAsync() + isNext |> should be True + let value = enumerator.Current // the first and only value + value |> should equal 42 + + // move past the end + let! isNext = enumerator.MoveNextAsync() + isNext |> should be False + let defaultValue = enumerator.Current // should return the default value for int + defaultValue |> should equal 0 + } diff --git a/src/FSharp.Control.TaskSeq.Test/TestUtils.fs b/src/FSharp.Control.TaskSeq.Test/TestUtils.fs index 91baf383..dd4873b3 100644 --- a/src/FSharp.Control.TaskSeq.Test/TestUtils.fs +++ b/src/FSharp.Control.TaskSeq.Test/TestUtils.fs @@ -141,6 +141,11 @@ module TestUtils = |> TaskSeq.toArrayAsync |> Task.map (Array.isEmpty >> should be True) + let verify1To10 ts = + ts + |> TaskSeq.toArrayAsync + |> Task.map (should equal [| 1..10 |]) + /// Delays (no spin-wait!) between 20 and 70ms, assuming a 15.6ms resolution clock let longDelay () = task { do! Task.Delay(Random().Next(20, 70)) } From d582041089c9ce2be217213fd50c7e0c202c33ac Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Fri, 25 Nov 2022 00:46:14 +0100 Subject: [PATCH 29/68] Fix code for proper results before/after enumerating singleton --- src/FSharp.Control.TaskSeq/TaskSeqInternal.fs | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs b/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs index 636be049..e2f2293a 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs +++ b/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs @@ -11,6 +11,12 @@ module ExtraTaskSeqOperators = /// A TaskSeq workflow for IAsyncEnumerable<'T> types. let taskSeq = TaskSeqBuilder() +[] +type AsyncEnumStatus = + | BeforeAll + | WithCurrent + | AfterAll + [] type Action<'T, 'U, 'TaskU when 'TaskU :> Task<'U>> = | CountableAction of countable_action: (int -> 'T -> 'U) @@ -64,15 +70,24 @@ module internal TaskSeqInternal = let singleton (source: 'T) = { new IAsyncEnumerable<'T> with member _.GetAsyncEnumerator(_) = - let mutable ended = false + let mutable status = BeforeAll { new IAsyncEnumerator<'T> with member _.MoveNextAsync() = - let vt = ValueTask.FromResult(not ended) - ended <- true - vt + match status with + | BeforeAll -> + status <- WithCurrent + ValueTask.True + | WithCurrent -> + status <- AfterAll + ValueTask.False + | AfterAll -> ValueTask.False + + member _.Current: 'T = + match status with + | WithCurrent -> source + | _ -> Unchecked.defaultof<'T> - member _.Current: 'T = if ended then Unchecked.defaultof<'T> else source member _.DisposeAsync() = ValueTask.CompletedTask } } From c1872ece6bb8f990c06ba7993fc3db5f45c88c04 Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Fri, 25 Nov 2022 00:46:32 +0100 Subject: [PATCH 30/68] Add a test for cornercase of multiple MoveNext after the end --- .../TaskSeq.Singleton.Tests.fs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Singleton.Tests.fs b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Singleton.Tests.fs index ea2d2368..a916b800 100644 --- a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Singleton.Tests.fs +++ b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Singleton.Tests.fs @@ -78,3 +78,19 @@ module Other = let defaultValue = enumerator.Current // should return the default value for int defaultValue |> should equal 0 } + + [] + let ``TaskSeq-singleton multiple MoveNext is fine`` () = task { + let enumerator = (TaskSeq.singleton 42).GetAsyncEnumerator() + let! isNext = enumerator.MoveNextAsync() + isNext |> should be True + let! _ = enumerator.MoveNextAsync() + let! _ = enumerator.MoveNextAsync() + let! _ = enumerator.MoveNextAsync() + let! isNext = enumerator.MoveNextAsync() + isNext |> should be False + + // should return the default value for int after moving past the end + let defaultValue = enumerator.Current + defaultValue |> should equal 0 + } From 227d35b64ab433968b4de745545c89792bce2817 Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Fri, 25 Nov 2022 01:48:56 +0100 Subject: [PATCH 31/68] Update readme.md with `singleton` --- README.md | 2 +- assets/nuget-package-readme.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ca208da5..f56b2d11 100644 --- a/README.md +++ b/README.md @@ -266,7 +266,7 @@ The following is the progress report: | ❓ | `rev` | | | [note #1](#note1 "These functions require a form of pre-materializing through 'TaskSeq.cache', similar to the approach taken in the corresponding 'Seq' functions. It doesn't make much sense to have a cached async sequence. However, 'AsyncSeq' does implement these, so we'll probably do so eventually as well.") | | | `scan` | `scan` | `scanAsync` | | | 🚫 | `scanBack` | | | [note #2](#note2 "Because of the async nature of TaskSeq sequences, iterating from the back would be bad practice. Instead, materialize the sequence to a list or array and then apply the 'Back' iterators.") | -| | `singleton` | `singleton` | | | +| ✅ [#90][] | `singleton` | `singleton` | | | | | `skip` | `skip` | | | | | `skipWhile` | `skipWhile` | `skipWhileAsync` | | | ❓ | `sort` | | | [note #1](#note1 "These functions require a form of pre-materializing through 'TaskSeq.cache', similar to the approach taken in the corresponding 'Seq' functions. It doesn't make much sense to have a cached async sequence. However, 'AsyncSeq' does implement these, so we'll probably do so eventually as well.") | diff --git a/assets/nuget-package-readme.md b/assets/nuget-package-readme.md index 6fa010bf..19cf16ab 100644 --- a/assets/nuget-package-readme.md +++ b/assets/nuget-package-readme.md @@ -183,7 +183,7 @@ The following is the progress report: | ❓ | `rev` | | | [note #1](#note-1 "These functions require a form of pre-materializing through 'TaskSeq.cache', similar to the approach taken in the corresponding 'Seq' functions. It doesn't make much sense to have a cached async sequence. However, 'AsyncSeq' does implement these, so we'll probably do so eventually as well.") | | | `scan` | `scan` | `scanAsync` | | | 🚫 | `scanBack` | | | [note #2](#note-2 "Because of the async nature of TaskSeq sequences, iterating from the back would be bad practice. Instead, materialize the sequence to a list or array and then apply the 'Back' iterators.") | -| | `singleton` | `singleton` | | | +| ✅ [#90][] | `singleton` | `singleton` | | | | | `skip` | `skip` | | | | | `skipWhile` | `skipWhile` | `skipWhileAsync` | | | ❓ | `sort` | | | [note #1](#note-1 "These functions require a form of pre-materializing through 'TaskSeq.cache', similar to the approach taken in the corresponding 'Seq' functions. It doesn't make much sense to have a cached async sequence. However, 'AsyncSeq' does implement these, so we'll probably do so eventually as well.") | From b860cb22aea047f34927a9d491fcc14b39fda837 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 25 Nov 2022 14:13:00 +0000 Subject: [PATCH 32/68] Bump actions/upload-artifact from 2 to 3 Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 2 to 3. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v2...v3) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/test.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index c71f1d54..aca9b8d7 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -25,7 +25,7 @@ jobs: run: ./build.cmd ci -release # upload test results - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 if: success() || failure() with: name: test-results-release @@ -55,7 +55,7 @@ jobs: run: ./build.cmd ci -debug # upload test results - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 if: success() || failure() with: name: test-results-debug From 230fce37fa45a84cefa1c55f3b9820789f51fbfe Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Fri, 25 Nov 2022 15:57:38 +0100 Subject: [PATCH 33/68] Fix function overview in readme with `singleton` --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f56b2d11..6a68a725 100644 --- a/README.md +++ b/README.md @@ -471,6 +471,7 @@ module TaskSeq = val pick: chooser: ('T -> 'U option) -> source: taskSeq<'T> -> Task<'U> val pickAsync: chooser: ('T -> #Task<'U option>) -> source: taskSeq<'T> -> Task<'U> val prependSeq: source1: #seq<'T> -> source2: #taskSeq<'T> -> taskSeq<'T> + val singleton: source: 'T -> taskSeq<'T> val tail: source: taskSeq<'T> -> Task> val toArray: source: taskSeq<'T> -> 'T[] val toArrayAsync: source: taskSeq<'T> -> Task<'T[]> From 05e4e0a32973997d0ce366425833285f5143a30f Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Mon, 14 Nov 2022 04:28:43 +0100 Subject: [PATCH 34/68] Initial attempt at implementing resumable code aware version of `For` in `Task` with `TaskSeq<'T>` --- src/FSharp.Control.TaskSeq/TaskSeq.fs | 32 +++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/FSharp.Control.TaskSeq/TaskSeq.fs b/src/FSharp.Control.TaskSeq/TaskSeq.fs index fb45a1ae..7f6cb51a 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeq.fs +++ b/src/FSharp.Control.TaskSeq/TaskSeq.fs @@ -4,6 +4,8 @@ open System.Collections.Generic open System.Threading open System.Threading.Tasks +#nowarn "57" + module TaskSeq = // F# BUG: the following module is 'AutoOpen' and this isn't needed in the Tests project. Why do we need to open it? open FSharp.Control.TaskSeqBuilders @@ -322,3 +324,33 @@ module TaskSeq = let fold folder state source = Internal.fold (FolderAction folder) state source let foldAsync folder state source = Internal.fold (AsyncFolderAction folder) state source + +[] +module AsyncSeqExtensions = + open Microsoft.FSharp.Core.CompilerServices + + // Add asynchronous for loop to the 'async' computation builder + type Microsoft.FSharp.Control.AsyncBuilder with + + member x.For(tasksq: IAsyncEnumerable<'T>, action: 'T -> Async) = + tasksq + |> TaskSeq.iterAsync (action >> Async.StartAsTask) + |> Async.AwaitTask + + // Add asynchronous for loop to the 'task' computation builder + type Microsoft.FSharp.Control.TaskBuilder with + + member inline this.For + ( + tasksq: IAsyncEnumerable<'T>, + body: 'T -> TaskCode<'TOverall, unit> + ) : TaskCode<'TOverall, unit> = + TaskCode<'TOverall, unit>(fun sm -> + this + .Using( + tasksq.GetAsyncEnumerator(CancellationToken()), + (fun e -> + // TODO: fix 'true' with e.MoveNextAsync() + this.While((fun () -> true), (fun sm -> (body e.Current).Invoke(&sm)))) + ) + .Invoke(&sm)) From 4f67c13f3d981fd816b7fe4307be96b257c0fb93 Mon Sep 17 00:00:00 2001 From: Jimmy Byrd Date: Mon, 21 Nov 2022 22:34:31 -0500 Subject: [PATCH 35/68] WhileDynamic for task files --- .../FSharp.Control.TaskSeq.Test.fsproj | 6 +- .../TaskSeq.Extensions.Tests.fs | 28 ++++++++ src/FSharp.Control.TaskSeq/TaskSeq.fs | 68 ++++++++++++++++++- src/FSharp.Control.TaskSeq/TaskSeq.fsi | 35 ++++++++++ 4 files changed, 130 insertions(+), 7 deletions(-) create mode 100644 src/FSharp.Control.TaskSeq.Test/TaskSeq.Extensions.Tests.fs diff --git a/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj b/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj index fdeb70de..d7c3e65e 100644 --- a/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj +++ b/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj @@ -2,12 +2,10 @@ net6.0 - false false ..\..\assets\TaskSeq.ico - @@ -48,9 +46,9 @@ + - @@ -68,9 +66,7 @@ all - - diff --git a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Extensions.Tests.fs b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Extensions.Tests.fs new file mode 100644 index 00000000..0b30e575 --- /dev/null +++ b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Extensions.Tests.fs @@ -0,0 +1,28 @@ +module TaskSeq.Extenions + +open System +open Xunit +open FsUnit.Xunit +open FsToolkit.ErrorHandling + +open FSharp.Control + +// +// TaskSeq.except +// TaskSeq.exceptOfSeq +// + + +// module TaskBuilder = +// open TaskSeq.Tests + +// [)>] +// let ``TaskSeq-existsAsync happy path last item of seq`` variant = +// task { +// let values = Gen.getSeqImmutable variant +// let mutable sum = 0 +// for x in values do +// sum <- sum + x +// } + // |> TaskSeq.existsAsync (fun x -> task { return x = 10 }) + // |> Task.map (should be True) diff --git a/src/FSharp.Control.TaskSeq/TaskSeq.fs b/src/FSharp.Control.TaskSeq/TaskSeq.fs index 7f6cb51a..bf9dbf1f 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeq.fs +++ b/src/FSharp.Control.TaskSeq/TaskSeq.fs @@ -325,8 +325,54 @@ module TaskSeq = let foldAsync folder state source = Internal.fold (AsyncFolderAction folder) state source +#nowarn "1204" +#nowarn "3513" + + [] module AsyncSeqExtensions = + + let rec WhileDynamic + ( + sm: byref>, + condition: unit -> ValueTask, + body: TaskCode<'Data, unit> + ) : bool = + let vt = condition () + TaskBuilderBase.BindDynamic(&sm, vt, fun result -> + TaskCode<_,_>(fun sm -> + if result then + if body.Invoke(&sm) then + WhileDynamic(&sm, condition, body) + else + let rf = sm.ResumptionDynamicInfo.ResumptionFunc + + sm.ResumptionDynamicInfo.ResumptionFunc <- + (TaskResumptionFunc<'Data>(fun sm -> WhileBodyDynamicAux(&sm, condition, body, rf))) + + false + else + true + ) + ) + + + and WhileBodyDynamicAux + ( + sm: byref>, + condition: unit -> ValueTask, + body: TaskCode<'Data, unit>, + rf: TaskResumptionFunc<_> + ) : bool = + if rf.Invoke(&sm) then + WhileDynamic(&sm, condition, body) + else + let rf = sm.ResumptionDynamicInfo.ResumptionFunc + + sm.ResumptionDynamicInfo.ResumptionFunc <- + (TaskResumptionFunc<'Data>(fun sm -> WhileBodyDynamicAux(&sm, condition, body, rf))) + + false open Microsoft.FSharp.Core.CompilerServices // Add asynchronous for loop to the 'async' computation builder @@ -340,17 +386,35 @@ module AsyncSeqExtensions = // Add asynchronous for loop to the 'task' computation builder type Microsoft.FSharp.Control.TaskBuilder with + + member inline this.While(condition : unit -> ValueTask, body : TaskCode<'TOverall,unit>) = + TaskCode<_,_>(fun sm -> + WhileDynamic(&sm, condition, body) + + ) + + + member inline this.For ( tasksq: IAsyncEnumerable<'T>, body: 'T -> TaskCode<'TOverall, unit> ) : TaskCode<'TOverall, unit> = TaskCode<'TOverall, unit>(fun sm -> + this .Using( tasksq.GetAsyncEnumerator(CancellationToken()), (fun e -> - // TODO: fix 'true' with e.MoveNextAsync() - this.While((fun () -> true), (fun sm -> (body e.Current).Invoke(&sm)))) + let next () = e.MoveNextAsync() + this.While(next, (fun sm -> (body e.Current).Invoke(&sm)))) ) .Invoke(&sm)) + + let foo () = + task { + let mutable sum = 0 + let xs = taskSeq { 1; 2; 3} + for x in xs do + sum <- sum + x + } diff --git a/src/FSharp.Control.TaskSeq/TaskSeq.fsi b/src/FSharp.Control.TaskSeq/TaskSeq.fsi index 0d9fe0c9..63fdb00d 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeq.fsi +++ b/src/FSharp.Control.TaskSeq/TaskSeq.fsi @@ -1,5 +1,7 @@ namespace FSharp.Control +#nowarn "1204" + module TaskSeq = open System.Collections.Generic open System.Threading.Tasks @@ -567,3 +569,36 @@ module TaskSeq = /// If the accumulator function does not need to be asynchronous, consider using . /// val foldAsync: folder: ('State -> 'T -> #Task<'State>) -> state: 'State -> source: taskSeq<'T> -> Task<'State> + + + +[] +module AsyncSeqExtensions = + + val WhileDynamic: + sm: byref> * + condition: (unit -> System.Threading.Tasks.ValueTask) * + body: TaskCode<'Data, unit> -> + bool + + val WhileBodyDynamicAux: + sm: byref> * + condition: (unit -> System.Threading.Tasks.ValueTask) * + body: TaskCode<'Data, unit> * + rf: TaskResumptionFunc<'Data> -> + bool + + type AsyncBuilder with + + member For: tasksq: System.Collections.Generic.IAsyncEnumerable<'T> * action: ('T -> Async) -> Async + + type TaskBuilder with + + member inline While: + condition: (unit -> System.Threading.Tasks.ValueTask) * body: TaskCode<'TOverall, unit> -> + TaskCode<'TOverall, 'a> + + member inline For: + tasksq: System.Collections.Generic.IAsyncEnumerable<'T> * body: ('T -> TaskCode<'TOverall, unit>) -> + TaskCode<'TOverall, unit> + From fd859939068875fa1054d180d1da2d23ec4bf473 Mon Sep 17 00:00:00 2001 From: Jimmy Byrd Date: Tue, 22 Nov 2022 00:27:30 -0500 Subject: [PATCH 36/68] WhileAsync statically compiled --- .../TaskSeq.Extensions.Tests.fs | 31 ++++++++------ src/FSharp.Control.TaskSeq/TaskSeq.fs | 42 ++++++++++++++++--- src/FSharp.Control.TaskSeq/TaskSeq.fsi | 4 +- 3 files changed, 57 insertions(+), 20 deletions(-) diff --git a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Extensions.Tests.fs b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Extensions.Tests.fs index 0b30e575..6fc7ad6a 100644 --- a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Extensions.Tests.fs +++ b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Extensions.Tests.fs @@ -13,16 +13,21 @@ open FSharp.Control // -// module TaskBuilder = -// open TaskSeq.Tests - -// [)>] -// let ``TaskSeq-existsAsync happy path last item of seq`` variant = -// task { -// let values = Gen.getSeqImmutable variant -// let mutable sum = 0 -// for x in values do -// sum <- sum + x -// } - // |> TaskSeq.existsAsync (fun x -> task { return x = 10 }) - // |> Task.map (should be True) +module TaskBuilder = + open TaskSeq.Tests + + [)>] + let ``TaskSeq-existsAsync happy path last item of seq`` variant = + task { + let values = Gen.getSeqImmutable variant + + let mutable sum = 0 + for x in values do + sum <- sum + x + + // let! expected = + // (0, values) + // ||> TaskSeq.fold((+)) + Assert.Equal(55, sum) + } + diff --git a/src/FSharp.Control.TaskSeq/TaskSeq.fs b/src/FSharp.Control.TaskSeq/TaskSeq.fs index bf9dbf1f..7831e436 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeq.fs +++ b/src/FSharp.Control.TaskSeq/TaskSeq.fs @@ -374,6 +374,8 @@ module AsyncSeqExtensions = false open Microsoft.FSharp.Core.CompilerServices + open Microsoft.FSharp.Core.CompilerServices.StateMachineHelpers + open Microsoft.FSharp.Core.LanguagePrimitives.IntrinsicOperators // Add asynchronous for loop to the 'async' computation builder type Microsoft.FSharp.Control.AsyncBuilder with @@ -387,14 +389,44 @@ module AsyncSeqExtensions = type Microsoft.FSharp.Control.TaskBuilder with - member inline this.While(condition : unit -> ValueTask, body : TaskCode<'TOverall,unit>) = - TaskCode<_,_>(fun sm -> - WhileDynamic(&sm, condition, body) + member inline _.WhileAsync + ( + [] condition: unit -> ValueTask, + body: TaskCode<_,unit> + ) : TaskCode<_,_> = + let mutable condition_res = true + + ResumableCode.While( + (fun () -> condition_res), + ResumableCode<_, _>(fun sm -> + let mutable __stack_condition_fin = true + let __stack_vtask = condition () + + let mutable awaiter = __stack_vtask.GetAwaiter() + if awaiter.IsCompleted then + // logInfo "at WhileAsync: returning completed task" + + __stack_condition_fin <- true + condition_res <- __stack_vtask.Result + else + // logInfo "at WhileAsync: awaiting non-completed task" - ) + // This will yield with __stack_fin = false + // This will resume with __stack_fin = true + let __stack_yield_fin = ResumableCode.Yield().Invoke(&sm) + __stack_condition_fin <- __stack_yield_fin + if __stack_condition_fin then + condition_res <- awaiter.GetResult() + if __stack_condition_fin then + if condition_res then body.Invoke(&sm) else true + else + sm.Data.MethodBuilder.AwaitUnsafeOnCompleted(&awaiter, &sm) + false) + ) + member inline this.For ( tasksq: IAsyncEnumerable<'T>, @@ -407,7 +439,7 @@ module AsyncSeqExtensions = tasksq.GetAsyncEnumerator(CancellationToken()), (fun e -> let next () = e.MoveNextAsync() - this.While(next, (fun sm -> (body e.Current).Invoke(&sm)))) + this.WhileAsync(next, (fun sm -> (body e.Current).Invoke(&sm)))) ) .Invoke(&sm)) diff --git a/src/FSharp.Control.TaskSeq/TaskSeq.fsi b/src/FSharp.Control.TaskSeq/TaskSeq.fsi index 63fdb00d..7df46d46 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeq.fsi +++ b/src/FSharp.Control.TaskSeq/TaskSeq.fsi @@ -594,9 +594,9 @@ module AsyncSeqExtensions = type TaskBuilder with - member inline While: + member inline WhileAsync: condition: (unit -> System.Threading.Tasks.ValueTask) * body: TaskCode<'TOverall, unit> -> - TaskCode<'TOverall, 'a> + TaskCode<'TOverall, unit> member inline For: tasksq: System.Collections.Generic.IAsyncEnumerable<'T> * body: ('T -> TaskCode<'TOverall, unit>) -> From aa918423feb3a78c508b118767aaada5768439b8 Mon Sep 17 00:00:00 2001 From: Jimmy Byrd Date: Tue, 22 Nov 2022 20:43:36 -0500 Subject: [PATCH 37/68] 3 different ways to possibly implement WhileAsync --- src/FSharp.Control.TaskSeq/TaskSeq.fs | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/FSharp.Control.TaskSeq/TaskSeq.fs b/src/FSharp.Control.TaskSeq/TaskSeq.fs index 7831e436..a328ded2 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeq.fs +++ b/src/FSharp.Control.TaskSeq/TaskSeq.fs @@ -398,7 +398,7 @@ module AsyncSeqExtensions = ResumableCode.While( (fun () -> condition_res), - ResumableCode<_, _>(fun sm -> + TaskCode<_, _>(fun sm -> let mutable __stack_condition_fin = true let __stack_vtask = condition () @@ -407,7 +407,7 @@ module AsyncSeqExtensions = // logInfo "at WhileAsync: returning completed task" __stack_condition_fin <- true - condition_res <- __stack_vtask.Result + condition_res <- awaiter.GetResult() else // logInfo "at WhileAsync: awaiting non-completed task" @@ -430,16 +430,31 @@ module AsyncSeqExtensions = member inline this.For ( tasksq: IAsyncEnumerable<'T>, - body: 'T -> TaskCode<'TOverall, unit> - ) : TaskCode<'TOverall, unit> = + body: 'T -> TaskCode<_, unit> + ) : TaskCode<_, unit> = + // tasksq + // |> TaskSeq.iterAsync (body >> task.Run) + // |> task.ReturnFrom + + // task.ReturnFrom <| + // task { + // let mutable continueWhile = true + // use e = tasksq.GetAsyncEnumerator() + // while continueWhile do + // let! next = e.MoveNextAsync() + // if next then + // do! task.Run(body e.Current) + // else + // continueWhile <- false + // } + TaskCode<'TOverall, unit>(fun sm -> this .Using( tasksq.GetAsyncEnumerator(CancellationToken()), (fun e -> - let next () = e.MoveNextAsync() - this.WhileAsync(next, (fun sm -> (body e.Current).Invoke(&sm)))) + this.WhileAsync(e.MoveNextAsync, (fun sm -> (body e.Current).Invoke(&sm)))) ) .Invoke(&sm)) From 1662b60f00aa4aed3da588e80481e7d9b7ad953c Mon Sep 17 00:00:00 2001 From: Jimmy Byrd Date: Mon, 21 Nov 2022 22:34:31 -0500 Subject: [PATCH 38/68] WhileDynamic for task files --- src/FSharp.Control.TaskSeq.Test/TaskSeq.Extensions.Tests.fs | 1 - src/FSharp.Control.TaskSeq/TaskSeq.fsi | 1 - 2 files changed, 2 deletions(-) diff --git a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Extensions.Tests.fs b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Extensions.Tests.fs index 6fc7ad6a..7cbd1b08 100644 --- a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Extensions.Tests.fs +++ b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Extensions.Tests.fs @@ -30,4 +30,3 @@ module TaskBuilder = // ||> TaskSeq.fold((+)) Assert.Equal(55, sum) } - diff --git a/src/FSharp.Control.TaskSeq/TaskSeq.fsi b/src/FSharp.Control.TaskSeq/TaskSeq.fsi index 7df46d46..fe556101 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeq.fsi +++ b/src/FSharp.Control.TaskSeq/TaskSeq.fsi @@ -601,4 +601,3 @@ module AsyncSeqExtensions = member inline For: tasksq: System.Collections.Generic.IAsyncEnumerable<'T> * body: ('T -> TaskCode<'TOverall, unit>) -> TaskCode<'TOverall, unit> - From c1ebe06e1206887a22ede4c7153da4a3a182211f Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Fri, 25 Nov 2022 16:06:07 +0100 Subject: [PATCH 39/68] Simplify diff --- .../FSharp.Control.TaskSeq.Test.fsproj | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj b/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj index d7c3e65e..38215053 100644 --- a/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj +++ b/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj @@ -2,10 +2,12 @@ net6.0 + false false ..\..\assets\TaskSeq.ico + @@ -49,6 +51,7 @@ + @@ -66,7 +69,9 @@ all + + From da024100fa4be248d9cb140c2841234921afdc2d Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Fri, 25 Nov 2022 16:24:30 +0100 Subject: [PATCH 40/68] Code reorganisation/refactoring for `async` and `task` CE extensions --- .../TaskSeq.Extensions.Tests.fs | 45 +++--- src/FSharp.Control.TaskSeq/AsyncExtensions.fs | 36 +++++ .../AsyncExtensions.fsi | 10 ++ .../FSharp.Control.TaskSeq.fsproj | 9 +- src/FSharp.Control.TaskSeq/TaskExtensions.fs | 145 ++++++++++++++++++ src/FSharp.Control.TaskSeq/TaskExtensions.fsi | 29 ++++ src/FSharp.Control.TaskSeq/TaskSeq.fs | 141 ----------------- src/FSharp.Control.TaskSeq/TaskSeq.fsi | 32 ---- src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs | 4 +- 9 files changed, 251 insertions(+), 200 deletions(-) create mode 100644 src/FSharp.Control.TaskSeq/AsyncExtensions.fs create mode 100644 src/FSharp.Control.TaskSeq/AsyncExtensions.fsi create mode 100644 src/FSharp.Control.TaskSeq/TaskExtensions.fs create mode 100644 src/FSharp.Control.TaskSeq/TaskExtensions.fsi diff --git a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Extensions.Tests.fs b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Extensions.Tests.fs index 7cbd1b08..682fb18e 100644 --- a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Extensions.Tests.fs +++ b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Extensions.Tests.fs @@ -1,32 +1,39 @@ -module TaskSeq.Extenions +module TaskSeq.Tests.Extensions open System open Xunit open FsUnit.Xunit -open FsToolkit.ErrorHandling open FSharp.Control // -// TaskSeq.except -// TaskSeq.exceptOfSeq +// Task extensions +// Async extensions // -module TaskBuilder = - open TaskSeq.Tests +module TaskCE = + [)>] + let ``Task-for CE with taskSeq`` variant = task { + let values = Gen.getSeqImmutable variant + + let mutable sum = 0 + + for x in values do + sum <- sum + x + + sum |> should equal 55 + } +module AsyncCE = [)>] - let ``TaskSeq-existsAsync happy path last item of seq`` variant = - task { - let values = Gen.getSeqImmutable variant - - let mutable sum = 0 - for x in values do - sum <- sum + x - - // let! expected = - // (0, values) - // ||> TaskSeq.fold((+)) - Assert.Equal(55, sum) - } + let ``Async-for CE with taskSeq`` variant = async { + let values = Gen.getSeqImmutable variant + + let mutable sum = 0 + + for x in values do + sum <- sum + x + + sum |> should equal 55 + } diff --git a/src/FSharp.Control.TaskSeq/AsyncExtensions.fs b/src/FSharp.Control.TaskSeq/AsyncExtensions.fs new file mode 100644 index 00000000..b7e86654 --- /dev/null +++ b/src/FSharp.Control.TaskSeq/AsyncExtensions.fs @@ -0,0 +1,36 @@ +namespace FSharp.Control + +open System.Collections.Generic +open System.Threading +open System.Threading.Tasks + +#nowarn "57" +#nowarn "1204" +#nowarn "3513" + + +[] +module AsyncExtensions = + + // Add asynchronous for loop to the 'async' computation builder + type Microsoft.FSharp.Control.AsyncBuilder with + + member x.For(tasksq: IAsyncEnumerable<'T>, action: 'T -> Async) = + tasksq + |> TaskSeq.iterAsync (action >> Async.StartAsTask) + |> Async.AwaitTask + + + // temp example + let foo () = async { + let mutable sum = 0 + + let xs = taskSeq { + 1 + 2 + 3 + } + + for x in xs do + sum <- sum + x + } diff --git a/src/FSharp.Control.TaskSeq/AsyncExtensions.fsi b/src/FSharp.Control.TaskSeq/AsyncExtensions.fsi new file mode 100644 index 00000000..7e5a7228 --- /dev/null +++ b/src/FSharp.Control.TaskSeq/AsyncExtensions.fsi @@ -0,0 +1,10 @@ +namespace FSharp.Control + +#nowarn "1204" + +[] +module AsyncExtensions = + + type AsyncBuilder with + + member For: tasksq: System.Collections.Generic.IAsyncEnumerable<'T> * action: ('T -> Async) -> Async diff --git a/src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj b/src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj index ffe66c2f..f6d1d6c1 100644 --- a/src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj +++ b/src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj @@ -71,13 +71,12 @@ Generates optimized IL code through the new resumable state machines, and comes + + + + - - - - - diff --git a/src/FSharp.Control.TaskSeq/TaskExtensions.fs b/src/FSharp.Control.TaskSeq/TaskExtensions.fs new file mode 100644 index 00000000..0a2ace5d --- /dev/null +++ b/src/FSharp.Control.TaskSeq/TaskExtensions.fs @@ -0,0 +1,145 @@ +namespace FSharp.Control + +open System.Collections.Generic +open System.Threading +open System.Threading.Tasks + +#nowarn "57" +#nowarn "1204" +#nowarn "3513" + + +[] +module TaskExtensions = + open Microsoft.FSharp.Core.CompilerServices + open Microsoft.FSharp.Core.CompilerServices.StateMachineHelpers + open Microsoft.FSharp.Core.LanguagePrimitives.IntrinsicOperators + + let rec WhileDynamic + ( + sm: byref>, + condition: unit -> ValueTask, + body: TaskCode<'Data, unit> + ) : bool = + let vt = condition () + + TaskBuilderBase.BindDynamic( + &sm, + vt, + fun result -> + TaskCode<_, _>(fun sm -> + if result then + if body.Invoke(&sm) then + WhileDynamic(&sm, condition, body) + else + let rf = sm.ResumptionDynamicInfo.ResumptionFunc + + sm.ResumptionDynamicInfo.ResumptionFunc <- + (TaskResumptionFunc<'Data>(fun sm -> WhileBodyDynamicAux(&sm, condition, body, rf))) + + false + else + true) + ) + + + and WhileBodyDynamicAux + ( + sm: byref>, + condition: unit -> ValueTask, + body: TaskCode<'Data, unit>, + rf: TaskResumptionFunc<_> + ) : bool = + if rf.Invoke(&sm) then + WhileDynamic(&sm, condition, body) + else + let rf = sm.ResumptionDynamicInfo.ResumptionFunc + + sm.ResumptionDynamicInfo.ResumptionFunc <- + (TaskResumptionFunc<'Data>(fun sm -> WhileBodyDynamicAux(&sm, condition, body, rf))) + + false + + // Add asynchronous for loop to the 'task' computation builder + type Microsoft.FSharp.Control.TaskBuilder with + + /// Used by `For`. F# currently doesn't support `while!`, so this cannot be called directly from the task CE + /// This code is mostly a copy of TaskSeq.WhileAsync. + member inline _.WhileAsync + ( + [] condition: unit -> ValueTask, + body: TaskCode<_, unit> + ) : TaskCode<_, _> = + let mutable condition_res = true + + ResumableCode.While( + (fun () -> condition_res), + TaskCode<_, _>(fun sm -> + let mutable __stack_condition_fin = true + let __stack_vtask = condition () + + let mutable awaiter = __stack_vtask.GetAwaiter() + + if awaiter.IsCompleted then + // logInfo "at WhileAsync: returning completed task" + + __stack_condition_fin <- true + condition_res <- awaiter.GetResult() + else + // logInfo "at WhileAsync: awaiting non-completed task" + + // This will yield with __stack_fin = false + // This will resume with __stack_fin = true + let __stack_yield_fin = ResumableCode.Yield().Invoke(&sm) + __stack_condition_fin <- __stack_yield_fin + + if __stack_condition_fin then + condition_res <- awaiter.GetResult() + + + if __stack_condition_fin then + if condition_res then body.Invoke(&sm) else true + else + sm.Data.MethodBuilder.AwaitUnsafeOnCompleted(&awaiter, &sm) + false) + ) + + member inline this.For(tasksq: IAsyncEnumerable<'T>, body: 'T -> TaskCode<_, unit>) : TaskCode<_, unit> = + // tasksq + // |> TaskSeq.iterAsync (body >> task.Run) + // |> task.ReturnFrom + + // task.ReturnFrom <| + // task { + // let mutable continueWhile = true + // use e = tasksq.GetAsyncEnumerator() + // while continueWhile do + // let! next = e.MoveNextAsync() + // if next then + // do! task.Run(body e.Current) + // else + // continueWhile <- false + // } + + TaskCode<'TOverall, unit>(fun sm -> + + this + .Using( + tasksq.GetAsyncEnumerator(CancellationToken()), + (fun e -> this.WhileAsync(e.MoveNextAsync, (fun sm -> (body e.Current).Invoke(&sm)))) + ) + .Invoke(&sm)) + + // temp example + let foo () = task { + let mutable sum = 0 + + let xs = taskSeq { + 1 + 2 + 3 + } + + for x in xs do + sum <- sum + x + } diff --git a/src/FSharp.Control.TaskSeq/TaskExtensions.fsi b/src/FSharp.Control.TaskSeq/TaskExtensions.fsi new file mode 100644 index 00000000..2fbc9930 --- /dev/null +++ b/src/FSharp.Control.TaskSeq/TaskExtensions.fsi @@ -0,0 +1,29 @@ +namespace FSharp.Control + +#nowarn "1204" + +[] +module TaskExtensions = + + val WhileDynamic: + sm: byref> * + condition: (unit -> System.Threading.Tasks.ValueTask) * + body: TaskCode<'Data, unit> -> + bool + + val WhileBodyDynamicAux: + sm: byref> * + condition: (unit -> System.Threading.Tasks.ValueTask) * + body: TaskCode<'Data, unit> * + rf: TaskResumptionFunc<'Data> -> + bool + + type TaskBuilder with + + member inline WhileAsync: + condition: (unit -> System.Threading.Tasks.ValueTask) * body: TaskCode<'TOverall, unit> -> + TaskCode<'TOverall, unit> + + member inline For: + tasksq: System.Collections.Generic.IAsyncEnumerable<'T> * body: ('T -> TaskCode<'TOverall, unit>) -> + TaskCode<'TOverall, unit> diff --git a/src/FSharp.Control.TaskSeq/TaskSeq.fs b/src/FSharp.Control.TaskSeq/TaskSeq.fs index a328ded2..e1a13d1b 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeq.fs +++ b/src/FSharp.Control.TaskSeq/TaskSeq.fs @@ -324,144 +324,3 @@ module TaskSeq = let fold folder state source = Internal.fold (FolderAction folder) state source let foldAsync folder state source = Internal.fold (AsyncFolderAction folder) state source - -#nowarn "1204" -#nowarn "3513" - - -[] -module AsyncSeqExtensions = - - let rec WhileDynamic - ( - sm: byref>, - condition: unit -> ValueTask, - body: TaskCode<'Data, unit> - ) : bool = - let vt = condition () - TaskBuilderBase.BindDynamic(&sm, vt, fun result -> - TaskCode<_,_>(fun sm -> - if result then - if body.Invoke(&sm) then - WhileDynamic(&sm, condition, body) - else - let rf = sm.ResumptionDynamicInfo.ResumptionFunc - - sm.ResumptionDynamicInfo.ResumptionFunc <- - (TaskResumptionFunc<'Data>(fun sm -> WhileBodyDynamicAux(&sm, condition, body, rf))) - - false - else - true - ) - ) - - - and WhileBodyDynamicAux - ( - sm: byref>, - condition: unit -> ValueTask, - body: TaskCode<'Data, unit>, - rf: TaskResumptionFunc<_> - ) : bool = - if rf.Invoke(&sm) then - WhileDynamic(&sm, condition, body) - else - let rf = sm.ResumptionDynamicInfo.ResumptionFunc - - sm.ResumptionDynamicInfo.ResumptionFunc <- - (TaskResumptionFunc<'Data>(fun sm -> WhileBodyDynamicAux(&sm, condition, body, rf))) - - false - open Microsoft.FSharp.Core.CompilerServices - open Microsoft.FSharp.Core.CompilerServices.StateMachineHelpers - open Microsoft.FSharp.Core.LanguagePrimitives.IntrinsicOperators - - // Add asynchronous for loop to the 'async' computation builder - type Microsoft.FSharp.Control.AsyncBuilder with - - member x.For(tasksq: IAsyncEnumerable<'T>, action: 'T -> Async) = - tasksq - |> TaskSeq.iterAsync (action >> Async.StartAsTask) - |> Async.AwaitTask - - // Add asynchronous for loop to the 'task' computation builder - type Microsoft.FSharp.Control.TaskBuilder with - - - member inline _.WhileAsync - ( - [] condition: unit -> ValueTask, - body: TaskCode<_,unit> - ) : TaskCode<_,_> = - let mutable condition_res = true - - ResumableCode.While( - (fun () -> condition_res), - TaskCode<_, _>(fun sm -> - let mutable __stack_condition_fin = true - let __stack_vtask = condition () - - let mutable awaiter = __stack_vtask.GetAwaiter() - if awaiter.IsCompleted then - // logInfo "at WhileAsync: returning completed task" - - __stack_condition_fin <- true - condition_res <- awaiter.GetResult() - else - // logInfo "at WhileAsync: awaiting non-completed task" - - // This will yield with __stack_fin = false - // This will resume with __stack_fin = true - let __stack_yield_fin = ResumableCode.Yield().Invoke(&sm) - __stack_condition_fin <- __stack_yield_fin - - if __stack_condition_fin then - condition_res <- awaiter.GetResult() - - - if __stack_condition_fin then - if condition_res then body.Invoke(&sm) else true - else - sm.Data.MethodBuilder.AwaitUnsafeOnCompleted(&awaiter, &sm) - false) - ) - - member inline this.For - ( - tasksq: IAsyncEnumerable<'T>, - body: 'T -> TaskCode<_, unit> - ) : TaskCode<_, unit> = - // tasksq - // |> TaskSeq.iterAsync (body >> task.Run) - // |> task.ReturnFrom - - // task.ReturnFrom <| - // task { - // let mutable continueWhile = true - // use e = tasksq.GetAsyncEnumerator() - // while continueWhile do - // let! next = e.MoveNextAsync() - // if next then - // do! task.Run(body e.Current) - // else - // continueWhile <- false - // } - - TaskCode<'TOverall, unit>(fun sm -> - - this - .Using( - tasksq.GetAsyncEnumerator(CancellationToken()), - (fun e -> - this.WhileAsync(e.MoveNextAsync, (fun sm -> (body e.Current).Invoke(&sm)))) - ) - .Invoke(&sm)) - - let foo () = - task { - let mutable sum = 0 - let xs = taskSeq { 1; 2; 3} - for x in xs do - sum <- sum + x - } diff --git a/src/FSharp.Control.TaskSeq/TaskSeq.fsi b/src/FSharp.Control.TaskSeq/TaskSeq.fsi index fe556101..172ab83c 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeq.fsi +++ b/src/FSharp.Control.TaskSeq/TaskSeq.fsi @@ -569,35 +569,3 @@ module TaskSeq = /// If the accumulator function does not need to be asynchronous, consider using . /// val foldAsync: folder: ('State -> 'T -> #Task<'State>) -> state: 'State -> source: taskSeq<'T> -> Task<'State> - - - -[] -module AsyncSeqExtensions = - - val WhileDynamic: - sm: byref> * - condition: (unit -> System.Threading.Tasks.ValueTask) * - body: TaskCode<'Data, unit> -> - bool - - val WhileBodyDynamicAux: - sm: byref> * - condition: (unit -> System.Threading.Tasks.ValueTask) * - body: TaskCode<'Data, unit> * - rf: TaskResumptionFunc<'Data> -> - bool - - type AsyncBuilder with - - member For: tasksq: System.Collections.Generic.IAsyncEnumerable<'T> * action: ('T -> Async) -> Async - - type TaskBuilder with - - member inline WhileAsync: - condition: (unit -> System.Threading.Tasks.ValueTask) * body: TaskCode<'TOverall, unit> -> - TaskCode<'TOverall, unit> - - member inline For: - tasksq: System.Collections.Generic.IAsyncEnumerable<'T> * body: ('T -> TaskCode<'TOverall, unit>) -> - TaskCode<'TOverall, unit> diff --git a/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs b/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs index 2331fc28..a1709bb4 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs +++ b/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs @@ -448,6 +448,7 @@ type TaskSeqBuilder() = ResumableCode.Combine(task1, task2) + /// Used by `For`. F# currently doesn't support `while!`, so this cannot be called directly from the CE member inline _.WhileAsync ( [] condition: unit -> ValueTask, @@ -490,9 +491,6 @@ type TaskSeqBuilder() = member inline b.While([] condition: unit -> bool, body: TaskSeqCode<'T>) : TaskSeqCode<'T> = logInfo "at While(...)" - - // was this: - // b.WhileAsync((fun () -> ValueTask(condition ())), body) ResumableCode.While(condition, body) member inline _.TryWith(body: TaskSeqCode<'T>, catch: exn -> TaskSeqCode<'T>) : TaskSeqCode<'T> = From 2299022870b12d0b3d64bc7b8aa341111f5667b6 Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Sat, 26 Nov 2022 17:51:34 +0100 Subject: [PATCH 41/68] Rename and split extension tests file between async and task --- .../FSharp.Control.TaskSeq.Test.fsproj | 3 +- ...ts.fs => TaskSeq.AsyncExtensions.Tests.fs} | 0 .../TaskSeq.TaskExtensions.Tests.fs | 39 +++++++++++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) rename src/FSharp.Control.TaskSeq.Test/{TaskSeq.Extensions.Tests.fs => TaskSeq.AsyncExtensions.Tests.fs} (100%) create mode 100644 src/FSharp.Control.TaskSeq.Test/TaskSeq.TaskExtensions.Tests.fs diff --git a/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj b/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj index 38215053..02c3b80d 100644 --- a/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj +++ b/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj @@ -48,7 +48,8 @@ - + + diff --git a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Extensions.Tests.fs b/src/FSharp.Control.TaskSeq.Test/TaskSeq.AsyncExtensions.Tests.fs similarity index 100% rename from src/FSharp.Control.TaskSeq.Test/TaskSeq.Extensions.Tests.fs rename to src/FSharp.Control.TaskSeq.Test/TaskSeq.AsyncExtensions.Tests.fs diff --git a/src/FSharp.Control.TaskSeq.Test/TaskSeq.TaskExtensions.Tests.fs b/src/FSharp.Control.TaskSeq.Test/TaskSeq.TaskExtensions.Tests.fs new file mode 100644 index 00000000..a3934086 --- /dev/null +++ b/src/FSharp.Control.TaskSeq.Test/TaskSeq.TaskExtensions.Tests.fs @@ -0,0 +1,39 @@ +module TaskSeq.Tests.TaskExtensions + +open System +open Xunit +open FsUnit.Xunit + +open FSharp.Control + +// +// Task extensions +// Async extensions +// + + +module TaskCE = + [)>] + let ``Task-for CE with taskSeq`` variant = task { + let values = Gen.getSeqImmutable variant + + let mutable sum = 0 + + for x in values do + sum <- sum + x + + sum |> should equal 55 + } + +module AsyncCE = + [)>] + let ``Async-for CE with taskSeq`` variant = async { + let values = Gen.getSeqImmutable variant + + let mutable sum = 0 + + for x in values do + sum <- sum + x + + sum |> should equal 55 + } From 1bd4e71709f9eb2190de158ed2ee692e042b638b Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Sat, 26 Nov 2022 18:07:56 +0100 Subject: [PATCH 42/68] Add more tests for corner cases of async-for and task-for --- .../TaskSeq.AsyncExtensions.Tests.fs | 90 +++++++++++++++++-- .../TaskSeq.TaskExtensions.Tests.fs | 85 +++++++++++++++++- 2 files changed, 165 insertions(+), 10 deletions(-) diff --git a/src/FSharp.Control.TaskSeq.Test/TaskSeq.AsyncExtensions.Tests.fs b/src/FSharp.Control.TaskSeq.Test/TaskSeq.AsyncExtensions.Tests.fs index 682fb18e..608085de 100644 --- a/src/FSharp.Control.TaskSeq.Test/TaskSeq.AsyncExtensions.Tests.fs +++ b/src/FSharp.Control.TaskSeq.Test/TaskSeq.AsyncExtensions.Tests.fs @@ -1,4 +1,4 @@ -module TaskSeq.Tests.Extensions +module TaskSeq.Tests.AsyncExtensions open System open Xunit @@ -7,14 +7,37 @@ open FsUnit.Xunit open FSharp.Control // -// Task extensions // Async extensions // +module EmptySeq = + [)>] + let ``Async-for CE with empty taskSeq`` variant = async { + let values = Gen.getEmptyVariant variant -module TaskCE = + let mutable sum = 42 + + for x in values do + sum <- sum + x + + sum |> should equal 42 + } + + [] + let ``Async-for CE must execute side effect in empty taskseq`` () = async { + let mutable data = 0 + let values = taskSeq { do data <- 42 } + + for x in values do + () + + data |> should equal 42 + } + + +module Immutable = [)>] - let ``Task-for CE with taskSeq`` variant = task { + let ``Async-for CE with taskSeq`` variant = async { let values = Gen.getSeqImmutable variant let mutable sum = 0 @@ -25,15 +48,70 @@ module TaskCE = sum |> should equal 55 } -module AsyncCE = [)>] - let ``Async-for CE with taskSeq`` variant = async { + let ``Async-for CE with taskSeq multiple iterations`` variant = async { let values = Gen.getSeqImmutable variant let mutable sum = 0 + for x in values do + sum <- sum + x + + // each following iteration should start at the beginning + for x in values do + sum <- sum + x + + for x in values do + sum <- sum + x + + sum |> should equal 165 + } + + [] + let ``Async-for mixing both types of for loops`` () = async { + // this test ensures overload resolution is correct + let ts = TaskSeq.singleton 20 + let sq = Seq.singleton 20 + let mutable sum = 2 + + for x in ts do + sum <- sum + x + + for x in sq do + sum <- sum + x + + sum |> should equal 42 + } + +module SideEffects = + [)>] + let ``Async-for CE with taskSeq`` variant = async { + let values = Gen.getSeqWithSideEffect variant + + let mutable sum = 0 + for x in values do sum <- sum + x sum |> should equal 55 } + + [)>] + let ``Async-for CE with taskSeq multiple iterations`` variant = async { + let values = Gen.getSeqWithSideEffect variant + + let mutable sum = 0 + + for x in values do + sum <- sum + x + + // each following iteration should start at the beginning + // with the "side effect" tests, the mutable state updates + for x in values do + sum <- sum + x // starts at 11 + + for x in values do + sum <- sum + x // starts at 21 + + sum |> should equal 465 // eq to: List.sum [1..30] + } diff --git a/src/FSharp.Control.TaskSeq.Test/TaskSeq.TaskExtensions.Tests.fs b/src/FSharp.Control.TaskSeq.Test/TaskSeq.TaskExtensions.Tests.fs index a3934086..9441f1fb 100644 --- a/src/FSharp.Control.TaskSeq.Test/TaskSeq.TaskExtensions.Tests.fs +++ b/src/FSharp.Control.TaskSeq.Test/TaskSeq.TaskExtensions.Tests.fs @@ -8,11 +8,33 @@ open FSharp.Control // // Task extensions -// Async extensions // +module EmptySeq = + [)>] + let ``Task-for CE with empty taskSeq`` variant = task { + let values = Gen.getEmptyVariant variant -module TaskCE = + let mutable sum = 42 + + for x in values do + sum <- sum + x + + sum |> should equal 42 + } + + [] + let ``Task-for CE must execute side effect in empty taskseq`` () = task { + let mutable data = 0 + let values = taskSeq { do data <- 42 } + + for x in values do + () + + data |> should equal 42 + } + +module Immutable = [)>] let ``Task-for CE with taskSeq`` variant = task { let values = Gen.getSeqImmutable variant @@ -25,15 +47,70 @@ module TaskCE = sum |> should equal 55 } -module AsyncCE = [)>] - let ``Async-for CE with taskSeq`` variant = async { + let ``Task-for CE with taskSeq multiple iterations`` variant = task { let values = Gen.getSeqImmutable variant let mutable sum = 0 + for x in values do + sum <- sum + x + + // each following iteration should start at the beginning + for x in values do + sum <- sum + x + + for x in values do + sum <- sum + x + + sum |> should equal 165 + } + + [] + let ``Task-for mixing both types of for loops`` () = async { + // this test ensures overload resolution is correct + let ts = TaskSeq.singleton 20 + let sq = Seq.singleton 20 + let mutable sum = 2 + + for x in ts do + sum <- sum + x + + for x in sq do + sum <- sum + x + + sum |> should equal 42 + } + +module SideEffects = + [)>] + let ``Task-for CE with taskSeq`` variant = task { + let values = Gen.getSeqWithSideEffect variant + + let mutable sum = 0 + for x in values do sum <- sum + x sum |> should equal 55 } + + [)>] + let ``Task-for CE with taskSeq multiple iterations`` variant = task { + let values = Gen.getSeqWithSideEffect variant + + let mutable sum = 0 + + for x in values do + sum <- sum + x + + // each following iteration should start at the beginning + // with the "side effect" tests, the mutable state updates + for x in values do + sum <- sum + x // starts at 11 + + for x in values do + sum <- sum + x // starts at 21 + + sum |> should equal 465 // eq to: List.sum [1..30] + } From c5b24ec703a2c569c2978ecd8556299e7916438e Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Sat, 26 Nov 2022 18:28:22 +0100 Subject: [PATCH 43/68] Add tests to ensure DisposeAsync is called in task-for and async-for --- .../TaskSeq.AsyncExtensions.Tests.fs | 27 +++++++++++++ .../TaskSeq.TaskExtensions.Tests.fs | 27 +++++++++++++ src/FSharp.Control.TaskSeq.Test/TestUtils.fs | 38 +++++++++++++++++++ 3 files changed, 92 insertions(+) diff --git a/src/FSharp.Control.TaskSeq.Test/TaskSeq.AsyncExtensions.Tests.fs b/src/FSharp.Control.TaskSeq.Test/TaskSeq.AsyncExtensions.Tests.fs index 608085de..1f0b9efd 100644 --- a/src/FSharp.Control.TaskSeq.Test/TaskSeq.AsyncExtensions.Tests.fs +++ b/src/FSharp.Control.TaskSeq.Test/TaskSeq.AsyncExtensions.Tests.fs @@ -115,3 +115,30 @@ module SideEffects = sum |> should equal 465 // eq to: List.sum [1..30] } + +module Other = + [] + let ``Async-for CE must call dispose in empty taskSeq`` () = async { + let disposed = ref 0 + let values = Gen.getEmptyDisposableTaskSeq disposed + + for x in values do + () + + // the DisposeAsync should be called by now + disposed.Value |> should equal 1 + } + + [] + let ``Async-for CE must call dispose on singleton`` () = async { + let disposed = ref 0 + let mutable sum = 0 + let values = Gen.getSingletonDisposableTaskSeq disposed + + for x in values do + sum <- x + + // the DisposeAsync should be called by now + disposed.Value |> should equal 1 + sum |> should equal 42 + } diff --git a/src/FSharp.Control.TaskSeq.Test/TaskSeq.TaskExtensions.Tests.fs b/src/FSharp.Control.TaskSeq.Test/TaskSeq.TaskExtensions.Tests.fs index 9441f1fb..5d5316a4 100644 --- a/src/FSharp.Control.TaskSeq.Test/TaskSeq.TaskExtensions.Tests.fs +++ b/src/FSharp.Control.TaskSeq.Test/TaskSeq.TaskExtensions.Tests.fs @@ -114,3 +114,30 @@ module SideEffects = sum |> should equal 465 // eq to: List.sum [1..30] } + +module Other = + [] + let ``Task-for CE must call dispose in empty taskSeq`` () = async { + let disposed = ref 0 + let values = Gen.getEmptyDisposableTaskSeq disposed + + for x in values do + () + + // the DisposeAsync should be called by now + disposed.Value |> should equal 1 + } + + [] + let ``Task-for CE must call dispose on singleton`` () = async { + let disposed = ref 0 + let mutable sum = 0 + let values = Gen.getSingletonDisposableTaskSeq disposed + + for x in values do + sum <- x + + // the DisposeAsync should be called by now + disposed.Value |> should equal 1 + sum |> should equal 42 + } diff --git a/src/FSharp.Control.TaskSeq.Test/TestUtils.fs b/src/FSharp.Control.TaskSeq.Test/TestUtils.fs index dd4873b3..28b36031 100644 --- a/src/FSharp.Control.TaskSeq.Test/TestUtils.fs +++ b/src/FSharp.Control.TaskSeq.Test/TestUtils.fs @@ -521,6 +521,44 @@ module TestUtils = } | x -> failwithf "Invalid test variant: %A" x + /// An empty taskSeq that can be used with tests for checking if the dispose method gets called. + /// Will add 1 to the passed integer upon disposing. + let getEmptyDisposableTaskSeq (disposed: int ref) = + { new IAsyncEnumerable<'T> with + member _.GetAsyncEnumerator(_) = + { new IAsyncEnumerator<'T> with + member _.MoveNextAsync() = ValueTask.False + member _.Current = Unchecked.defaultof<'T> + member _.DisposeAsync() = ValueTask(task { do disposed.Value <- disposed.Value + 1 }) + } + } + + /// A singleton taskSeq that can be used with tests for checking if the dispose method gets called + /// The singleton value is '42'. Will add 1 to the passed integer upon disposing. + let getSingletonDisposableTaskSeq (disposed: int ref) = + { new IAsyncEnumerable with + member _.GetAsyncEnumerator(_) = + let mutable status = BeforeAll + + { new IAsyncEnumerator with + member _.MoveNextAsync() = + match status with + | BeforeAll -> + status <- WithCurrent + ValueTask.True + | WithCurrent -> + status <- AfterAll + ValueTask.False + | AfterAll -> ValueTask.False + + member _.Current: int = + match status with + | WithCurrent -> 42 + | _ -> Unchecked.defaultof + + member _.DisposeAsync() = ValueTask(task { do disposed.Value <- disposed.Value + 1 }) + } + } // // following types can be used with Theory & TestData // From 5bd38139577a55e4ff0620fd7daf8f2c77d6e134 Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Sat, 26 Nov 2022 18:45:56 +0100 Subject: [PATCH 44/68] Renames, cleanup and overall fixup of task-for and async-for CE code --- src/FSharp.Control.TaskSeq/AsyncExtensions.fs | 28 ++---------- .../AsyncExtensions.fsi | 6 +-- src/FSharp.Control.TaskSeq/TaskExtensions.fs | 44 +++---------------- src/FSharp.Control.TaskSeq/TaskExtensions.fsi | 22 +--------- 4 files changed, 15 insertions(+), 85 deletions(-) diff --git a/src/FSharp.Control.TaskSeq/AsyncExtensions.fs b/src/FSharp.Control.TaskSeq/AsyncExtensions.fs index b7e86654..51c46b4e 100644 --- a/src/FSharp.Control.TaskSeq/AsyncExtensions.fs +++ b/src/FSharp.Control.TaskSeq/AsyncExtensions.fs @@ -1,13 +1,6 @@ namespace FSharp.Control -open System.Collections.Generic -open System.Threading -open System.Threading.Tasks - -#nowarn "57" -#nowarn "1204" -#nowarn "3513" - +open FSharp.Control.TaskSeqBuilders [] module AsyncExtensions = @@ -15,22 +8,7 @@ module AsyncExtensions = // Add asynchronous for loop to the 'async' computation builder type Microsoft.FSharp.Control.AsyncBuilder with - member x.For(tasksq: IAsyncEnumerable<'T>, action: 'T -> Async) = - tasksq + member _.For(source: taskSeq<'T>, action: 'T -> Async) = + source |> TaskSeq.iterAsync (action >> Async.StartAsTask) |> Async.AwaitTask - - - // temp example - let foo () = async { - let mutable sum = 0 - - let xs = taskSeq { - 1 - 2 - 3 - } - - for x in xs do - sum <- sum + x - } diff --git a/src/FSharp.Control.TaskSeq/AsyncExtensions.fsi b/src/FSharp.Control.TaskSeq/AsyncExtensions.fsi index 7e5a7228..d4432756 100644 --- a/src/FSharp.Control.TaskSeq/AsyncExtensions.fsi +++ b/src/FSharp.Control.TaskSeq/AsyncExtensions.fsi @@ -1,10 +1,10 @@ namespace FSharp.Control -#nowarn "1204" - [] module AsyncExtensions = + open FSharp.Control.TaskSeqBuilders type AsyncBuilder with - member For: tasksq: System.Collections.Generic.IAsyncEnumerable<'T> * action: ('T -> Async) -> Async + /// Iterate over all values of a taskSeq. + member For: source: taskSeq<'T> * action: ('T -> Async) -> Async diff --git a/src/FSharp.Control.TaskSeq/TaskExtensions.fs b/src/FSharp.Control.TaskSeq/TaskExtensions.fs index 0a2ace5d..adb9b109 100644 --- a/src/FSharp.Control.TaskSeq/TaskExtensions.fs +++ b/src/FSharp.Control.TaskSeq/TaskExtensions.fs @@ -4,16 +4,17 @@ open System.Collections.Generic open System.Threading open System.Threading.Tasks +open Microsoft.FSharp.Core.CompilerServices +open Microsoft.FSharp.Core.LanguagePrimitives.IntrinsicOperators + +open FSharp.Control.TaskSeqBuilders + #nowarn "57" #nowarn "1204" #nowarn "3513" - [] module TaskExtensions = - open Microsoft.FSharp.Core.CompilerServices - open Microsoft.FSharp.Core.CompilerServices.StateMachineHelpers - open Microsoft.FSharp.Core.LanguagePrimitives.IntrinsicOperators let rec WhileDynamic ( @@ -104,42 +105,11 @@ module TaskExtensions = false) ) - member inline this.For(tasksq: IAsyncEnumerable<'T>, body: 'T -> TaskCode<_, unit>) : TaskCode<_, unit> = - // tasksq - // |> TaskSeq.iterAsync (body >> task.Run) - // |> task.ReturnFrom - - // task.ReturnFrom <| - // task { - // let mutable continueWhile = true - // use e = tasksq.GetAsyncEnumerator() - // while continueWhile do - // let! next = e.MoveNextAsync() - // if next then - // do! task.Run(body e.Current) - // else - // continueWhile <- false - // } - + member inline this.For(source: taskSeq<'T>, body: 'T -> TaskCode<_, unit>) : TaskCode<_, unit> = TaskCode<'TOverall, unit>(fun sm -> - this .Using( - tasksq.GetAsyncEnumerator(CancellationToken()), + source.GetAsyncEnumerator(CancellationToken()), (fun e -> this.WhileAsync(e.MoveNextAsync, (fun sm -> (body e.Current).Invoke(&sm)))) ) .Invoke(&sm)) - - // temp example - let foo () = task { - let mutable sum = 0 - - let xs = taskSeq { - 1 - 2 - 3 - } - - for x in xs do - sum <- sum + x - } diff --git a/src/FSharp.Control.TaskSeq/TaskExtensions.fsi b/src/FSharp.Control.TaskSeq/TaskExtensions.fsi index 2fbc9930..81a441a3 100644 --- a/src/FSharp.Control.TaskSeq/TaskExtensions.fsi +++ b/src/FSharp.Control.TaskSeq/TaskExtensions.fsi @@ -4,26 +4,8 @@ namespace FSharp.Control [] module TaskExtensions = - - val WhileDynamic: - sm: byref> * - condition: (unit -> System.Threading.Tasks.ValueTask) * - body: TaskCode<'Data, unit> -> - bool - - val WhileBodyDynamicAux: - sm: byref> * - condition: (unit -> System.Threading.Tasks.ValueTask) * - body: TaskCode<'Data, unit> * - rf: TaskResumptionFunc<'Data> -> - bool + open FSharp.Control.TaskSeqBuilders type TaskBuilder with - member inline WhileAsync: - condition: (unit -> System.Threading.Tasks.ValueTask) * body: TaskCode<'TOverall, unit> -> - TaskCode<'TOverall, unit> - - member inline For: - tasksq: System.Collections.Generic.IAsyncEnumerable<'T> * body: ('T -> TaskCode<'TOverall, unit>) -> - TaskCode<'TOverall, unit> + member inline For: source: taskSeq<'T> * body: ('T -> TaskCode<'TOverall, unit>) -> TaskCode<'TOverall, unit> From a4a229b03e7a53d65a061b9235a8bc0823032134 Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Sat, 26 Nov 2022 20:05:04 +0100 Subject: [PATCH 45/68] Remove WhileDynamic, it's already implicitly present, and add debug logging to the new code --- src/FSharp.Control.TaskSeq/TaskExtensions.fs | 51 +-------- src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs | 106 ++++++------------- src/FSharp.Control.TaskSeq/Utils.fs | 52 +++++++++ 3 files changed, 86 insertions(+), 123 deletions(-) diff --git a/src/FSharp.Control.TaskSeq/TaskExtensions.fs b/src/FSharp.Control.TaskSeq/TaskExtensions.fs index adb9b109..a5fac864 100644 --- a/src/FSharp.Control.TaskSeq/TaskExtensions.fs +++ b/src/FSharp.Control.TaskSeq/TaskExtensions.fs @@ -16,51 +16,6 @@ open FSharp.Control.TaskSeqBuilders [] module TaskExtensions = - let rec WhileDynamic - ( - sm: byref>, - condition: unit -> ValueTask, - body: TaskCode<'Data, unit> - ) : bool = - let vt = condition () - - TaskBuilderBase.BindDynamic( - &sm, - vt, - fun result -> - TaskCode<_, _>(fun sm -> - if result then - if body.Invoke(&sm) then - WhileDynamic(&sm, condition, body) - else - let rf = sm.ResumptionDynamicInfo.ResumptionFunc - - sm.ResumptionDynamicInfo.ResumptionFunc <- - (TaskResumptionFunc<'Data>(fun sm -> WhileBodyDynamicAux(&sm, condition, body, rf))) - - false - else - true) - ) - - - and WhileBodyDynamicAux - ( - sm: byref>, - condition: unit -> ValueTask, - body: TaskCode<'Data, unit>, - rf: TaskResumptionFunc<_> - ) : bool = - if rf.Invoke(&sm) then - WhileDynamic(&sm, condition, body) - else - let rf = sm.ResumptionDynamicInfo.ResumptionFunc - - sm.ResumptionDynamicInfo.ResumptionFunc <- - (TaskResumptionFunc<'Data>(fun sm -> WhileBodyDynamicAux(&sm, condition, body, rf))) - - false - // Add asynchronous for loop to the 'task' computation builder type Microsoft.FSharp.Control.TaskBuilder with @@ -73,6 +28,8 @@ module TaskExtensions = ) : TaskCode<_, _> = let mutable condition_res = true + // note that this While itself has both a dynamic and static implementation + // so we don't need to add that here (TODO: how to verify?). ResumableCode.While( (fun () -> condition_res), TaskCode<_, _>(fun sm -> @@ -82,12 +39,12 @@ module TaskExtensions = let mutable awaiter = __stack_vtask.GetAwaiter() if awaiter.IsCompleted then - // logInfo "at WhileAsync: returning completed task" + Debug.logInfo "at Task.WhileAsync: returning completed task" __stack_condition_fin <- true condition_res <- awaiter.GetResult() else - // logInfo "at WhileAsync: awaiting non-completed task" + Debug.logInfo "at Task.WhileAsync: awaiting non-completed task" // This will yield with __stack_fin = false // This will resume with __stack_fin = true diff --git a/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs b/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs index a1709bb4..195ed87b 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs +++ b/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs @@ -36,50 +36,6 @@ module Internal = // cannot be marked with 'internal' scope with _ -> false - type Debug = - - [] - static val mutable private verbose: bool option - - /// Setting from environment variable TASKSEQ_LOG_VERBOSE, which, - /// when set, enables (very) verbose printing of flow and state - static member private getVerboseSetting() = - match Debug.verbose with - | None -> - let verboseEnv = - try - match Environment.GetEnvironmentVariable "TASKSEQ_LOG_VERBOSE" with - | null -> false - | x -> - match x.ToLowerInvariant().Trim() with - | "1" - | "true" - | "on" - | "yes" -> true - | _ -> false - - with _ -> - false - - Debug.verbose <- Some verboseEnv - verboseEnv - - | Some setting -> setting - - [] - static member private print value = - match Debug.getVerboseSetting () with - | false -> () - | true -> - // don't use ksprintf here, because the compiler does not remove all allocations due to - // the way PrintfFormat types are compiled, even if we set the Conditional attribute. - printfn "%i (%b): %s" Thread.CurrentThread.ManagedThreadId Thread.CurrentThread.IsThreadPoolThread value - - [] - static member logInfo(str) = Debug.print str - - [] - static member logInfo(str, data) = Debug.print $"%s{str}{data}" /// Call MoveNext on an IAsyncStateMachine by reference let inline moveNextRef (x: byref<'T> when 'T :> IAsyncStateMachine) = x.MoveNext() @@ -89,8 +45,6 @@ module Internal = // cannot be marked with 'internal' scope NotImplementedException "Abstract Class: method or property not implemented" |> raise -open type Debug - type taskSeq<'T> = IAsyncEnumerable<'T> type IPriority1 = @@ -244,7 +198,7 @@ and [] TaskSeq<'Machine, 'T this // just return 'self' here | _ -> - logInfo "GetAsyncEnumerator, start cloning..." + Debug.logInfo "GetAsyncEnumerator, start cloning..." // We need to reset state, but only to the "initial machine", resetting the _machine to // Unchecked.defaultof<_> is wrong, as the compiler uses this to track state. However, @@ -260,7 +214,7 @@ and [] TaskSeq<'Machine, 'T clone._machine <- this._initialMachine clone._initialMachine <- this._initialMachine // TODO: proof with a test that this is necessary: probably not clone.InitMachineData(ct, &clone._machine) - logInfo "GetAsyncEnumerator, finished cloning..." + Debug.logInfo "GetAsyncEnumerator, finished cloning..." clone interface System.Collections.Generic.IAsyncEnumerator<'T> with @@ -275,39 +229,39 @@ and [] TaskSeq<'Machine, 'T Unchecked.defaultof<'T> member this.MoveNextAsync() = - logInfo "MoveNextAsync..." + Debug.logInfo "MoveNextAsync..." if this._machine.ResumptionPoint = -1 then // can't use as IAsyncEnumerator before IAsyncEnumerable - logInfo "at MoveNextAsync: Resumption point = -1" + Debug.logInfo "at MoveNextAsync: Resumption point = -1" ValueTask.False elif this._machine.Data.completed then - logInfo "at MoveNextAsync: completed = true" + Debug.logInfo "at MoveNextAsync: completed = true" // return False when beyond the last item this._machine.Data.promiseOfValueOrEnd.Reset() ValueTask.False else - logInfo "at MoveNextAsync: normal resumption scenario" + Debug.logInfo "at MoveNextAsync: normal resumption scenario" let data = this._machine.Data data.promiseOfValueOrEnd.Reset() let mutable ts = this - logInfo "at MoveNextAsync: start calling builder.MoveNext()" + Debug.logInfo "at MoveNextAsync: start calling builder.MoveNext()" data.builder.MoveNext(&ts) - logInfo "at MoveNextAsync: finished calling builder.MoveNext()" + Debug.logInfo "at MoveNextAsync: finished calling builder.MoveNext()" this.MoveNextAsyncResult() /// Disposes of the IAsyncEnumerator (*not* the IAsyncEnumerable!!!) member this.DisposeAsync() = task { - logInfo "DisposeAsync..." + Debug.logInfo "DisposeAsync..." match this._machine.Data.disposalStack with | null -> () @@ -335,7 +289,7 @@ and [] TaskSeq<'Machine, 'T match status with | ValueTaskSourceStatus.Succeeded -> - logInfo "at MoveNextAsyncResult: case succeeded..." + Debug.logInfo "at MoveNextAsyncResult: case succeeded..." let result = data.promiseOfValueOrEnd.GetResult(version) @@ -349,11 +303,11 @@ and [] TaskSeq<'Machine, 'T | ValueTaskSourceStatus.Faulted | ValueTaskSourceStatus.Canceled | ValueTaskSourceStatus.Pending as state -> - logInfo ("at MoveNextAsyncResult: case ", state) + Debug.logInfo ("at MoveNextAsyncResult: case ", state) ValueTask.ofIValueTaskSource this version | _ -> - logInfo "at MoveNextAsyncResult: Unexpected state" + Debug.logInfo "at MoveNextAsyncResult: Unexpected state" // assume it's a possibly new, not yet supported case, treat as default ValueTask.ofIValueTaskSource this version @@ -376,12 +330,12 @@ type TaskSeqBuilder() = __resumeAt sm.ResumptionPoint try - logInfo "at Run.MoveNext start" + Debug.logInfo "at Run.MoveNext start" let __stack_code_fin = code.Invoke(&sm) if __stack_code_fin then - logInfo $"at Run.MoveNext, done" + Debug.logInfo $"at Run.MoveNext, done" // Signal we're at the end // NOTE: if we don't do it here, as well as in IValueTaskSource.GetResult @@ -392,14 +346,14 @@ type TaskSeqBuilder() = sm.Data.completed <- true elif sm.Data.current.IsSome then - logInfo $"at Run.MoveNext, still more items in enumerator" + Debug.logInfo $"at Run.MoveNext, still more items in enumerator" // Signal there's more data: sm.Data.promiseOfValueOrEnd.SetResult(true) else // Goto request - logInfo $"at Run.MoveNext, await, MoveNextAsync has not completed yet" + Debug.logInfo $"at Run.MoveNext, await, MoveNextAsync has not completed yet" // don't capture the full object in the next closure (won't work because: byref) // but only a reference to itself. @@ -412,7 +366,7 @@ type TaskSeqBuilder() = ) with exn -> - logInfo ("Setting exception of PromiseOfValueOrEnd to: ", exn.Message) + Debug.logInfo ("Setting exception of PromiseOfValueOrEnd to: ", exn.Message) sm.Data.promiseOfValueOrEnd.SetException(exn) sm.Data.builder.Complete() @@ -420,7 +374,7 @@ type TaskSeqBuilder() = )) (SetStateMachineMethodImpl<_>(fun sm state -> ())) // not used in reference impl (AfterCode<_, _>(fun sm -> - logInfo "at AfterCode<_, _>, after F# inits the sm, and we can attach extra info" + Debug.logInfo "at AfterCode<_, _>, after F# inits the sm, and we can attach extra info" let ts = TaskSeq, 'T>() ts._initialMachine <- sm @@ -440,11 +394,11 @@ type TaskSeqBuilder() = member inline _.Zero() : TaskSeqCode<'T> = - logInfo "at Zero()" + Debug.logInfo "at Zero()" ResumableCode.Zero() member inline _.Combine(task1: TaskSeqCode<'T>, task2: TaskSeqCode<'T>) : TaskSeqCode<'T> = - logInfo "at Combine(.., ..)" + Debug.logInfo "at Combine(.., ..)" ResumableCode.Combine(task1, task2) @@ -463,12 +417,12 @@ type TaskSeqBuilder() = let __stack_vtask = condition () if __stack_vtask.IsCompleted then - logInfo "at WhileAsync: returning completed task" + Debug.logInfo "at WhileAsync: returning completed task" __stack_condition_fin <- true condition_res <- __stack_vtask.Result else - logInfo "at WhileAsync: awaiting non-completed task" + Debug.logInfo "at WhileAsync: awaiting non-completed task" let task = __stack_vtask.AsTask() let mutable awaiter = task.GetAwaiter() @@ -490,7 +444,7 @@ type TaskSeqBuilder() = ) member inline b.While([] condition: unit -> bool, body: TaskSeqCode<'T>) : TaskSeqCode<'T> = - logInfo "at While(...)" + Debug.logInfo "at While(...)" ResumableCode.While(condition, body) member inline _.TryWith(body: TaskSeqCode<'T>, catch: exn -> TaskSeqCode<'T>) : TaskSeqCode<'T> = @@ -595,7 +549,7 @@ type TaskSeqBuilder() = TaskSeqCode<'T>(fun sm -> // This will yield with __stack_fin = false // This will resume with __stack_fin = true - logInfo "at Yield" + Debug.logInfo "at Yield" let __stack_fin = ResumableCode.Yield().Invoke(&sm) sm.Data.current <- ValueSome v @@ -612,7 +566,7 @@ type TaskSeqBuilder() = let mutable awaiter = task.GetAwaiter() let mutable __stack_fin = true - logInfo "at Bind" + Debug.logInfo "at Bind" if not awaiter.IsCompleted then // This will yield with __stack_fin2 = false @@ -620,15 +574,15 @@ type TaskSeqBuilder() = let __stack_fin2 = ResumableCode.Yield().Invoke(&sm) __stack_fin <- __stack_fin2 - logInfo ("at Bind: with __stack_fin = ", __stack_fin) - logInfo ("at Bind: this.completed = ", sm.Data.completed) + Debug.logInfo ("at Bind: with __stack_fin = ", __stack_fin) + Debug.logInfo ("at Bind: this.completed = ", sm.Data.completed) if __stack_fin then let result = awaiter.GetResult() (continuation result).Invoke(&sm) else - logInfo "at Bind: calling AwaitUnsafeOnCompleted" + Debug.logInfo "at Bind: calling AwaitUnsafeOnCompleted" sm.Data.awaiter <- awaiter sm.Data.current <- ValueNone @@ -639,7 +593,7 @@ type TaskSeqBuilder() = let mutable awaiter = task.GetAwaiter() let mutable __stack_fin = true - logInfo "at BindV" + Debug.logInfo "at BindV" if not awaiter.IsCompleted then // This will yield with __stack_fin2 = false @@ -651,7 +605,7 @@ type TaskSeqBuilder() = let result = awaiter.GetResult() (continuation result).Invoke(&sm) else - logInfo "at BindV: calling AwaitUnsafeOnCompleted" + Debug.logInfo "at BindV: calling AwaitUnsafeOnCompleted" sm.Data.awaiter <- awaiter sm.Data.current <- ValueNone diff --git a/src/FSharp.Control.TaskSeq/Utils.fs b/src/FSharp.Control.TaskSeq/Utils.fs index 09d0e504..15fce9b2 100644 --- a/src/FSharp.Control.TaskSeq/Utils.fs +++ b/src/FSharp.Control.TaskSeq/Utils.fs @@ -1,6 +1,9 @@ namespace FSharp.Control open System.Threading.Tasks +open System +open System.Diagnostics +open System.Threading [] module ValueTaskExtensions = @@ -103,3 +106,52 @@ module Async = /// Bind an Async<'T> let inline bind binder (task: Async<'T>) : Async<'U> = ExtraTopLevelOperators.async { return! binder task } + +type Debug = + + [] + static val mutable private verbose: bool option + + /// Setting from environment variable TASKSEQ_LOG_VERBOSE, which, + /// when set, enables (very) verbose printing of flow and state + static member private getVerboseSetting() = + match Debug.verbose with + | None -> + let verboseEnv = + try + match Environment.GetEnvironmentVariable "TASKSEQ_LOG_VERBOSE" with + | null -> false + | x -> + match x.ToLowerInvariant().Trim() with + | "1" + | "true" + | "on" + | "yes" -> true + | _ -> false + + with _ -> + false + + Debug.verbose <- Some verboseEnv + verboseEnv + + | Some setting -> setting + + /// Private helper to log to stdout in DEBUG builds only + [] + static member private print value = + match Debug.getVerboseSetting () with + | false -> () + | true -> + // don't use ksprintf here, because the compiler does not remove all allocations due to + // the way PrintfFormat types are compiled, even if we set the Conditional attribute. + let ct = Thread.CurrentThread + printfn "%i (%b): %s" ct.ManagedThreadId ct.IsThreadPoolThread value + + /// Log to stdout in DEBUG builds only + [] + static member logInfo(str) = Debug.print str + + /// Log to stdout in DEBUG builds only + [] + static member logInfo(str, data) = Debug.print $"%s{str}{data}" From c30010aaca49a80de41d27afd9f37fdf3c187fa4 Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Sat, 26 Nov 2022 20:13:12 +0100 Subject: [PATCH 46/68] Add note on future usage of `__debugPoint` --- src/FSharp.Control.TaskSeq/TaskExtensions.fs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/FSharp.Control.TaskSeq/TaskExtensions.fs b/src/FSharp.Control.TaskSeq/TaskExtensions.fs index a5fac864..aa40599f 100644 --- a/src/FSharp.Control.TaskSeq/TaskExtensions.fs +++ b/src/FSharp.Control.TaskSeq/TaskExtensions.fs @@ -67,6 +67,16 @@ module TaskExtensions = this .Using( source.GetAsyncEnumerator(CancellationToken()), - (fun e -> this.WhileAsync(e.MoveNextAsync, (fun sm -> (body e.Current).Invoke(&sm)))) + (fun e -> + this.WhileAsync( + // __debugPoint is only available from FSharp.Core 6.0.4 + //(fun () -> + // Microsoft.FSharp.Core.CompilerServices.StateMachineHelpers.__debugPoint + // "ForLoop.InOrToKeyword" + + // e.MoveNextAsync()), + e.MoveNextAsync, + (fun sm -> (body e.Current).Invoke(&sm)) + )) ) .Invoke(&sm)) From 2729006a208374e92def940da52970cadd5324ec Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Sat, 26 Nov 2022 20:30:37 +0100 Subject: [PATCH 47/68] Add examples to readme.md and add doc comments to the fsi files and update release notes --- README.md | 22 ++++++++++++++----- assets/nuget-package-readme.md | 22 ++++++++++++++----- .../AsyncExtensions.fsi | 4 +++- .../FSharp.Control.TaskSeq.fsproj | 9 ++++---- src/FSharp.Control.TaskSeq/TaskExtensions.fsi | 3 +++ 5 files changed, 43 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 6a68a725..115958b9 100644 --- a/README.md +++ b/README.md @@ -29,8 +29,8 @@ Latest version [can be installed from Nuget][nuget]. - [Progress `taskSeq` CE](#progress-taskseq-ce) - [Progress and implemented `TaskSeq` module functions](#progress-and-implemented-taskseq-module-functions) - [More information](#more-information) - - [Futher reading `IAsyncEnumerable`](#futher-reading-iasyncenumerable) - - [Futher reading on resumable state machines](#futher-reading-on-resumable-state-machines) + - [Further reading `IAsyncEnumerable`](#further-reading-iasyncenumerable) + - [Further reading on resumable state machines](#further-reading-on-resumable-state-machines) - [Further reading on computation expressions](#further-reading-on-computation-expressions) - [Building & testing](#building--testing) - [Prerequisites](#prerequisites) @@ -109,15 +109,25 @@ As package reference in `fsproj` or `csproj` file: ```f# open System.IO - open FSharp.Control // singleton is fine -let hello = taskSeq { yield "Hello, World!" } +let helloTs = taskSeq { yield "Hello, World!" } + +// cold-started, that is, delay-executed +let f() = task { + // using toList forces execution of whole sequence + let! hello = TaskSeq.toList helloTs // toList returns a Task<'T list> + return List.head hello +} // can be mixed with normal sequences let oneToTen = taskSeq { yield! [1..10] } +// can be used with F#'s task and async in a for-loop +let f() = task { for x in oneToTen do printfn "Number %i" x } +let g() = async { for x in oneToTen do printfn "Number %i" x } + // returns a delayed sequence of IAsyncEnumerable let allFilesAsLines() = taskSeq { let files = Directory.EnumerateFiles(@"c:\temp") @@ -313,14 +323,14 @@ The following is the progress report: ## More information -### Futher reading `IAsyncEnumerable` +### Further reading `IAsyncEnumerable` - A good C#-based introduction [can be found in this blog][8]. - [An MSDN article][9] written shortly after it was introduced. - Converting a `seq` to an `IAsyncEnumerable` [demo gist][10] as an example, though `TaskSeq` contains many more utility functions and uses a slightly different approach. - If you're looking for using `IAsyncEnumerable` with `async` and not `task`, the excellent [`AsyncSeq`][11] library should be used. While `TaskSeq` is intended to consume `async` just like `task` does, it won't create an `AsyncSeq` type (at least not yet). If you want classic Async and parallelism, you should get this library instead. -### Futher reading on resumable state machines +### Further reading on resumable state machines - A state machine from a monadic perspective in F# [can be found here][12], which works with the pre-F# 6.0 non-resumable internals. - The [original RFC for F# 6.0 on resumable state machines][13] diff --git a/assets/nuget-package-readme.md b/assets/nuget-package-readme.md index 19cf16ab..7e106635 100644 --- a/assets/nuget-package-readme.md +++ b/assets/nuget-package-readme.md @@ -19,8 +19,8 @@ An implementation of [`IAsyncEnumerable<'T>`][3] as a computation expression: `t - [Examples](#examples) - [`TaskSeq` module functions](#taskseq-module-functions) - [More information](#more-information) - - [Futher reading `IAsyncEnumerable`](#futher-reading-iasyncenumerable) - - [Futher reading on resumable state machines](#futher-reading-on-resumable-state-machines) + - [Further reading `IAsyncEnumerable`](#further-reading-iasyncenumerable) + - [Further reading on resumable state machines](#further-reading-on-resumable-state-machines) - [Further reading on computation expressions](#further-reading-on-computation-expressions) ----------------------------------------- @@ -47,15 +47,25 @@ for `use` and `use!`, `try-with` and `try-finally` and `while` loops within the ```f# open System.IO - open FSharp.Control // singleton is fine -let hello = taskSeq { yield "Hello, World!" } +let helloTs = taskSeq { yield "Hello, World!" } + +// cold-started, that is, delay-executed +let f() = task { + // using toList forces execution of whole sequence + let! hello = TaskSeq.toList helloTs // toList returns a Task<'T list> + return List.head hello +} // can be mixed with normal sequences let oneToTen = taskSeq { yield! [1..10] } +// can be used with F#'s task and async in a for-loop +let f() = task { for x in oneToTen do printfn "Number %i" x } +let g() = async { for x in oneToTen do printfn "Number %i" x } + // returns a delayed sequence of IAsyncEnumerable let allFilesAsLines() = taskSeq { let files = Directory.EnumerateFiles(@"c:\temp") @@ -237,14 +247,14 @@ _The motivation for `readOnly` in `Seq` is that a cast from a mutable array or l ## More information -### Futher reading `IAsyncEnumerable` +### Further reading `IAsyncEnumerable` - A good C#-based introduction [can be found in this blog][8]. - [An MSDN article][9] written shortly after it was introduced. - Converting a `seq` to an `IAsyncEnumerable` [demo gist][10] as an example, though `TaskSeq` contains many more utility functions and uses a slightly different approach. - If you're looking for using `IAsyncEnumerable` with `async` and not `task`, the excellent [`AsyncSeq`][11] library should be used. While `TaskSeq` is intended to consume `async` just like `task` does, it won't create an `AsyncSeq` type (at least not yet). If you want classic Async and parallelism, you should get this library instead. -### Futher reading on resumable state machines +### Further reading on resumable state machines - A state machine from a monadic perspective in F# [can be found here][12], which works with the pre-F# 6.0 non-resumable internals. - The [original RFC for F# 6.0 on resumable state machines][13] diff --git a/src/FSharp.Control.TaskSeq/AsyncExtensions.fsi b/src/FSharp.Control.TaskSeq/AsyncExtensions.fsi index d4432756..7470eafd 100644 --- a/src/FSharp.Control.TaskSeq/AsyncExtensions.fsi +++ b/src/FSharp.Control.TaskSeq/AsyncExtensions.fsi @@ -6,5 +6,7 @@ module AsyncExtensions = type AsyncBuilder with - /// Iterate over all values of a taskSeq. + /// + /// Inside , iterate over all values of a . + /// member For: source: taskSeq<'T> * action: ('T -> Async) -> Async diff --git a/src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj b/src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj index f6d1d6c1..1575392d 100644 --- a/src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj +++ b/src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj @@ -23,10 +23,11 @@ Generates optimized IL code through the new resumable state machines, and comes nuget-package-readme.md Release notes: - 0.2.3 (unreleased) - - add TaskSeq.singleton, #90 (by @gusty) - - improve TaskSeq.empty by not relying on resumable state, #89 (by @gusty) - - do not throw exception for unequal lengths in TaskSeq.zip, fixes #32 + 0.3.0 (unreleased) + - adds support for 'for .. in ..' with task sequences in F# tasks and async, #75, #93 and #99 (with help from @theangrybyrd) + - adds TaskSeq.singleton, #90 (by @gusty) + - improves TaskSeq.empty by not relying on resumable state, #89 (by @gusty) + - does not throw exceptions anymore for unequal lengths in TaskSeq.zip, fixes #32 0.2.2 - removes TaskSeq.toSeqCachedAsync, which was incorrectly named. Use toSeq or toListAsync instead. - renames TaskSeq.toSeqCached to TaskSeq.toSeq, which was its actual operational behavior. diff --git a/src/FSharp.Control.TaskSeq/TaskExtensions.fsi b/src/FSharp.Control.TaskSeq/TaskExtensions.fsi index 81a441a3..b5a97e7d 100644 --- a/src/FSharp.Control.TaskSeq/TaskExtensions.fsi +++ b/src/FSharp.Control.TaskSeq/TaskExtensions.fsi @@ -8,4 +8,7 @@ module TaskExtensions = type TaskBuilder with + /// + /// Inside , iterate over all values of a . + /// member inline For: source: taskSeq<'T> * body: ('T -> TaskCode<'TOverall, unit>) -> TaskCode<'TOverall, unit> From 201f5c443192608782b5b3814a62ee761d63214b Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Sat, 26 Nov 2022 22:43:32 +0100 Subject: [PATCH 48/68] Fix link in readmes --- README.md | 1 + assets/nuget-package-readme.md | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 115958b9..8629ccaf 100644 --- a/README.md +++ b/README.md @@ -544,6 +544,7 @@ module TaskSeq = [#81]: https://github.com/fsprojects/FSharp.Control.TaskSeq/pull/81 [#82]: https://github.com/fsprojects/FSharp.Control.TaskSeq/pull/82 [#83]: https://github.com/fsprojects/FSharp.Control.TaskSeq/pull/83 +[#90]: https://github.com/fsprojects/FSharp.Control.TaskSeq/pull/90 [issues]: https://github.com/fsprojects/FSharp.Control.TaskSeq/issues [nuget]: https://www.nuget.org/packages/FSharp.Control.TaskSeq/ diff --git a/assets/nuget-package-readme.md b/assets/nuget-package-readme.md index 7e106635..35e6320c 100644 --- a/assets/nuget-package-readme.md +++ b/assets/nuget-package-readme.md @@ -296,4 +296,5 @@ _The motivation for `readOnly` in `Seq` is that a cast from a mutable array or l [#76]: https://github.com/fsprojects/FSharp.Control.TaskSeq/pull/76 [#81]: https://github.com/fsprojects/FSharp.Control.TaskSeq/pull/81 [#82]: https://github.com/fsprojects/FSharp.Control.TaskSeq/pull/82 -[#83]: https://github.com/fsprojects/FSharp.Control.TaskSeq/pull/83 \ No newline at end of file +[#83]: https://github.com/fsprojects/FSharp.Control.TaskSeq/pull/83 +[#90]: https://github.com/fsprojects/FSharp.Control.TaskSeq/pull/90 \ No newline at end of file From b7508fe279fc7651b46d14f1121f26a62f98da53 Mon Sep 17 00:00:00 2001 From: "Peter D. Faria" Date: Tue, 15 Nov 2022 13:45:43 -0500 Subject: [PATCH 49/68] Added test cases to showcase bug. When a type implements both IDisposable and IAsyncDisposable, F# cannot determine which overload of TaskSeqBuilder.Using to use. --- .../FSharp.Control.TaskSeq.Test.fsproj | 1 + .../TaskSeq.Using.Tests.fs | 97 +++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 src/FSharp.Control.TaskSeq.Test/TaskSeq.Using.Tests.fs diff --git a/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj b/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj index 02c3b80d..5a5d0df0 100644 --- a/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj +++ b/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj @@ -50,6 +50,7 @@ + diff --git a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Using.Tests.fs b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Using.Tests.fs new file mode 100644 index 00000000..004e8878 --- /dev/null +++ b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Using.Tests.fs @@ -0,0 +1,97 @@ +module FSharp.Control.TaskSeq.Test + +open System +open System.Threading.Tasks +open FSharp.Control +open FsUnit +open Xunit + + +type private OneGetter() = + member _.Get1() = 1 + +type private Disposable() = + inherit OneGetter() + + interface IDisposable with + member _.Dispose() = () + +type private AsyncDisposable() = + inherit OneGetter() + + interface IAsyncDisposable with + member _.DisposeAsync() = ValueTask() + +type private MultiDispose() = + inherit OneGetter() + + interface IDisposable with + member _.Dispose() = + () + + interface IAsyncDisposable with + member _.DisposeAsync() = + ValueTask() + +let private check ts = task { + let! length = ts |> TaskSeq.length + length |> should equal 1 +} + +[] +let ``CE task: Using when type implements IDisposable``() = + let ts = taskSeq { + use x = new Disposable() + + yield x.Get1() + } + + check ts + +[] +let ``CE task: Using when type implements IAsyncDisposable``() = + let ts = taskSeq { + use x = AsyncDisposable() + yield x.Get1() + } + + check ts + + +[] +let ``CE task: Using when type implements IDisposable and IAsyncDisposable``() = + let ts = taskSeq { + use x = new MultiDispose() + yield x.Get1() + } + + check ts + +[] +let ``CE task: Using! when type implements IDisposable``() = + let ts = taskSeq { + use! x = task { return new Disposable() } + yield x.Get1() + } + + check ts + + +[] +let ``CE task: Using! when type implements IAsyncDisposable``() = + let ts = taskSeq { + use! x = task { return AsyncDisposable() } + yield x.Get1() + } + + check ts + + +[] +let ``CE task: Using! when type implements IDisposable and IAsyncDisposable``() = + let ts = taskSeq { + use! x = task { return new MultiDispose() } + yield x.Get1() + } + + check ts From c2aa3a0bd8ad1dbbc7d157ca04bb16f843fc56e1 Mon Sep 17 00:00:00 2001 From: "Peter D. Faria" Date: Tue, 15 Nov 2022 13:49:35 -0500 Subject: [PATCH 50/68] Added comments showing which lines fail to compile. --- src/FSharp.Control.TaskSeq.Test/TaskSeq.Using.Tests.fs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Using.Tests.fs b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Using.Tests.fs index 004e8878..17c8e01f 100644 --- a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Using.Tests.fs +++ b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Using.Tests.fs @@ -61,7 +61,7 @@ let ``CE task: Using when type implements IAsyncDisposable``() = [] let ``CE task: Using when type implements IDisposable and IAsyncDisposable``() = let ts = taskSeq { - use x = new MultiDispose() + use x = new MultiDispose() // Fails to compile yield x.Get1() } @@ -90,7 +90,7 @@ let ``CE task: Using! when type implements IAsyncDisposable``() = [] let ``CE task: Using! when type implements IDisposable and IAsyncDisposable``() = let ts = taskSeq { - use! x = task { return new MultiDispose() } + use! x = task { return new MultiDispose() } // Fails to compile yield x.Get1() } From 71796485e1c2704820f4b18f93ac0492a15437f8 Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Sun, 27 Nov 2022 01:20:18 +0100 Subject: [PATCH 51/68] Implement priority-overload layers with modules, similar to task, to solve #97 `use` overload resolution bug --- src/FSharp.Control.TaskSeq/AsyncExtensions.fs | 2 - .../AsyncExtensions.fsi | 1 - src/FSharp.Control.TaskSeq/TaskExtensions.fs | 2 - src/FSharp.Control.TaskSeq/TaskExtensions.fsi | 1 - src/FSharp.Control.TaskSeq/TaskSeq.fs | 2 - src/FSharp.Control.TaskSeq/TaskSeq.fsi | 1 - src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs | 99 ++++++++++--------- src/FSharp.Control.TaskSeq/TaskSeqInternal.fs | 1 - 8 files changed, 51 insertions(+), 58 deletions(-) diff --git a/src/FSharp.Control.TaskSeq/AsyncExtensions.fs b/src/FSharp.Control.TaskSeq/AsyncExtensions.fs index 51c46b4e..25817526 100644 --- a/src/FSharp.Control.TaskSeq/AsyncExtensions.fs +++ b/src/FSharp.Control.TaskSeq/AsyncExtensions.fs @@ -1,7 +1,5 @@ namespace FSharp.Control -open FSharp.Control.TaskSeqBuilders - [] module AsyncExtensions = diff --git a/src/FSharp.Control.TaskSeq/AsyncExtensions.fsi b/src/FSharp.Control.TaskSeq/AsyncExtensions.fsi index 7470eafd..cf96281e 100644 --- a/src/FSharp.Control.TaskSeq/AsyncExtensions.fsi +++ b/src/FSharp.Control.TaskSeq/AsyncExtensions.fsi @@ -2,7 +2,6 @@ namespace FSharp.Control [] module AsyncExtensions = - open FSharp.Control.TaskSeqBuilders type AsyncBuilder with diff --git a/src/FSharp.Control.TaskSeq/TaskExtensions.fs b/src/FSharp.Control.TaskSeq/TaskExtensions.fs index aa40599f..b168f358 100644 --- a/src/FSharp.Control.TaskSeq/TaskExtensions.fs +++ b/src/FSharp.Control.TaskSeq/TaskExtensions.fs @@ -7,8 +7,6 @@ open System.Threading.Tasks open Microsoft.FSharp.Core.CompilerServices open Microsoft.FSharp.Core.LanguagePrimitives.IntrinsicOperators -open FSharp.Control.TaskSeqBuilders - #nowarn "57" #nowarn "1204" #nowarn "3513" diff --git a/src/FSharp.Control.TaskSeq/TaskExtensions.fsi b/src/FSharp.Control.TaskSeq/TaskExtensions.fsi index b5a97e7d..76cd8f9d 100644 --- a/src/FSharp.Control.TaskSeq/TaskExtensions.fsi +++ b/src/FSharp.Control.TaskSeq/TaskExtensions.fsi @@ -4,7 +4,6 @@ namespace FSharp.Control [] module TaskExtensions = - open FSharp.Control.TaskSeqBuilders type TaskBuilder with diff --git a/src/FSharp.Control.TaskSeq/TaskSeq.fs b/src/FSharp.Control.TaskSeq/TaskSeq.fs index e1a13d1b..05f5313c 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeq.fs +++ b/src/FSharp.Control.TaskSeq/TaskSeq.fs @@ -7,8 +7,6 @@ open System.Threading.Tasks #nowarn "57" module TaskSeq = - // F# BUG: the following module is 'AutoOpen' and this isn't needed in the Tests project. Why do we need to open it? - open FSharp.Control.TaskSeqBuilders // Just for convenience module Internal = TaskSeqInternal diff --git a/src/FSharp.Control.TaskSeq/TaskSeq.fsi b/src/FSharp.Control.TaskSeq/TaskSeq.fsi index 172ab83c..1f0f1497 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeq.fsi +++ b/src/FSharp.Control.TaskSeq/TaskSeq.fsi @@ -5,7 +5,6 @@ namespace FSharp.Control module TaskSeq = open System.Collections.Generic open System.Threading.Tasks - open FSharp.Control.TaskSeqBuilders /// Initialize an empty taskSeq. val empty<'T> : taskSeq<'T> diff --git a/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs b/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs index 195ed87b..ab48de43 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs +++ b/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs @@ -1,4 +1,4 @@ -namespace FSharp.Control.TaskSeqBuilders +namespace FSharp.Control open System.Diagnostics @@ -47,13 +47,6 @@ module Internal = // cannot be marked with 'internal' scope type taskSeq<'T> = IAsyncEnumerable<'T> -type IPriority1 = - interface - end - -type IPriority2 = - interface - end [] type TaskSeqStateMachineData<'T>() = @@ -489,15 +482,7 @@ type TaskSeqBuilder() = true) ) - member inline this.Using - ( - disp: #IDisposable, - body: #IDisposable -> TaskSeqCode<'T>, - ?priority: IPriority2 - ) : TaskSeqCode<'T> = - - // FIXME: what about priority? - ignore priority + member inline this.Using(disp: #IDisposable, body: #IDisposable -> TaskSeqCode<'T>) : TaskSeqCode<'T> = // A using statement is just a try/finally with the finally block disposing if non-null. this.TryFinally( @@ -508,26 +493,6 @@ type TaskSeqBuilder() = disp.Dispose()) ) - member inline this.Using - ( - disp: #IAsyncDisposable, - body: #IAsyncDisposable -> TaskSeqCode<'T>, - ?priority: IPriority1 - ) : TaskSeqCode<'T> = - - // FIXME: what about priorities? - ignore priority - - // A using statement is just a try/finally with the finally block disposing if non-null. - this.TryFinallyAsync( - (fun sm -> (body disp).Invoke(&sm)), - (fun () -> - if not (isNull (box disp)) then - disp.DisposeAsync().AsTask() - else - Task.CompletedTask) - ) - member inline this.For(sequence: seq<'TElement>, body: 'TElement -> TaskSeqCode<'T>) : TaskSeqCode<'T> = // A for loop is just a using statement on the sequence's enumerator... this.Using( @@ -536,14 +501,6 @@ type TaskSeqBuilder() = (fun e -> this.While((fun () -> e.MoveNext()), (fun sm -> (body e.Current).Invoke(&sm)))) ) - member inline this.For(source: #IAsyncEnumerable<'TElement>, body: 'TElement -> TaskSeqCode<'T>) : TaskSeqCode<'T> = - TaskSeqCode<'T>(fun sm -> - this - .Using( - source.GetAsyncEnumerator(sm.Data.cancellationToken), - (fun e -> this.WhileAsync((fun () -> e.MoveNextAsync()), (fun sm -> (body e.Current).Invoke(&sm)))) - ) - .Invoke(&sm)) member inline _.Yield(v: 'T) : TaskSeqCode<'T> = TaskSeqCode<'T>(fun sm -> @@ -556,9 +513,6 @@ type TaskSeqBuilder() = sm.Data.awaiter <- null __stack_fin) - member inline this.YieldFrom(source: IAsyncEnumerable<'T>) : TaskSeqCode<'T> = - this.For(source, (fun v -> this.Yield(v))) - member inline this.YieldFrom(source: seq<'T>) : TaskSeqCode<'T> = this.For(source, (fun v -> this.Yield(v))) member inline _.Bind(task: Task<'TResult1>, continuation: ('TResult1 -> TaskSeqCode<'T>)) : TaskSeqCode<'T> = @@ -610,3 +564,52 @@ type TaskSeqBuilder() = sm.Data.awaiter <- awaiter sm.Data.current <- ValueNone false) + +// +// These "modules of priority" allow for an indecisive F# to resolve +// the proper overload if a single type implements more than one +// interface. For instance, a type implementing 'IDisposable' and +// 'IAsyncDisposable'. +// +// See for more info tasks.fs in F# Core. +// +// This section also includes the dependencies of such overloads +// (like For depending on Using etc). +// + +[] +module MediumPriority = + type TaskSeqBuilder with + + member inline this.Using + ( + disp: #IAsyncDisposable, + body: #IAsyncDisposable -> TaskSeqCode<'T> + ) : TaskSeqCode<'T> = + + // A using statement is just a try/finally with the finally block disposing if non-null. + this.TryFinallyAsync( + (fun sm -> (body disp).Invoke(&sm)), + (fun () -> + if not (isNull (box disp)) then + disp.DisposeAsync().AsTask() + else + Task.CompletedTask) + ) + + member inline this.For + ( + source: #IAsyncEnumerable<'TElement>, + body: 'TElement -> TaskSeqCode<'T> + ) : TaskSeqCode<'T> = + TaskSeqCode<'T>(fun sm -> + this + .Using( + source.GetAsyncEnumerator(sm.Data.cancellationToken), + (fun e -> + this.WhileAsync((fun () -> e.MoveNextAsync()), (fun sm -> (body e.Current).Invoke(&sm)))) + ) + .Invoke(&sm)) + + member inline this.YieldFrom(source: IAsyncEnumerable<'T>) : TaskSeqCode<'T> = + this.For(source, (fun v -> this.Yield(v))) diff --git a/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs b/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs index e2f2293a..deeaefbd 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs +++ b/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs @@ -4,7 +4,6 @@ open System open System.Collections.Generic open System.Threading open System.Threading.Tasks -open FSharp.Control.TaskSeqBuilders [] module ExtraTaskSeqOperators = From 5f855b2700472a988cd295a92fb15c7aec575e5f Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Sun, 27 Nov 2022 01:21:21 +0100 Subject: [PATCH 52/68] Improve tests to ensure the Dispose/DisposeAsync is actually called --- .../TaskSeq.Using.Tests.fs | 67 +++++++++++-------- 1 file changed, 38 insertions(+), 29 deletions(-) diff --git a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Using.Tests.fs b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Using.Tests.fs index 17c8e01f..fc5a62c7 100644 --- a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Using.Tests.fs +++ b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Using.Tests.fs @@ -10,88 +10,97 @@ open Xunit type private OneGetter() = member _.Get1() = 1 -type private Disposable() = +type private Disposable(disposed: bool ref) = inherit OneGetter() interface IDisposable with - member _.Dispose() = () + member _.Dispose() = disposed.Value <- true -type private AsyncDisposable() = +type private AsyncDisposable(disposed: bool ref) = inherit OneGetter() interface IAsyncDisposable with - member _.DisposeAsync() = ValueTask() + member _.DisposeAsync() = ValueTask(task { do disposed.Value <- true }) -type private MultiDispose() = +type private MultiDispose(disposed: int ref) = inherit OneGetter() interface IDisposable with - member _.Dispose() = - () + member _.Dispose() = disposed.Value <- !disposed + 1 interface IAsyncDisposable with - member _.DisposeAsync() = - ValueTask() + member _.DisposeAsync() = ValueTask(task { do disposed.Value <- !disposed + 1 }) -let private check ts = task { - let! length = ts |> TaskSeq.length - length |> should equal 1 -} +let private check = TaskSeq.length >> Task.map (should equal 1) [] -let ``CE task: Using when type implements IDisposable``() = - let ts = taskSeq { - use x = new Disposable() +let ``CE task: Using when type implements IDisposable`` () = + let disposed = ref false + let ts = taskSeq { + use x = new Disposable(disposed) yield x.Get1() } check ts + |> Task.map (fun _ -> disposed.Value |> should be True) [] -let ``CE task: Using when type implements IAsyncDisposable``() = +let ``CE task: Using when type implements IAsyncDisposable`` () = + let disposed = ref false + let ts = taskSeq { - use x = AsyncDisposable() + use x = AsyncDisposable(disposed) yield x.Get1() } check ts - + |> Task.map (fun _ -> disposed.Value |> should be True) [] -let ``CE task: Using when type implements IDisposable and IAsyncDisposable``() = +let ``CE task: Using when type implements IDisposable and IAsyncDisposable`` () = + let disposed = ref 0 + let ts = taskSeq { - use x = new MultiDispose() // Fails to compile + use x = new MultiDispose(disposed) // Used to fail to compile (see #97) yield x.Get1() } check ts + |> Task.map (fun _ -> disposed.Value |> should equal 1) // only one of the two dispose method should fire [] -let ``CE task: Using! when type implements IDisposable``() = +let ``CE task: Using! when type implements IDisposable`` () = + let disposed = ref false + let ts = taskSeq { - use! x = task { return new Disposable() } + use! x = task { return new Disposable(disposed) } yield x.Get1() } check ts - + |> Task.map (fun _ -> disposed.Value |> should be True) [] -let ``CE task: Using! when type implements IAsyncDisposable``() = +let ``CE task: Using! when type implements IAsyncDisposable`` () = + let disposed = ref false + let ts = taskSeq { - use! x = task { return AsyncDisposable() } + use! x = task { return AsyncDisposable(disposed) } yield x.Get1() } check ts - + |> Task.map (fun _ -> disposed.Value |> should be True) [] -let ``CE task: Using! when type implements IDisposable and IAsyncDisposable``() = +let ``CE task: Using! when type implements IDisposable and IAsyncDisposable`` () = + let disposed = ref 0 + let ts = taskSeq { - use! x = task { return new MultiDispose() } // Fails to compile + use! x = task { return new MultiDispose(disposed) } // Used to fail to compile (see #97) yield x.Get1() } check ts + |> Task.map (fun _ -> disposed.Value |> should equal 1) // only one of the two dispose method should fire From 79b57bb38c1c40fe0c1845f2411d235b1d1292a4 Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Sun, 27 Nov 2022 01:23:10 +0100 Subject: [PATCH 53/68] Update changelog --- src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj b/src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj index 1575392d..c276bfce 100644 --- a/src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj +++ b/src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj @@ -24,10 +24,11 @@ Generates optimized IL code through the new resumable state machines, and comes Release notes: 0.3.0 (unreleased) - - adds support for 'for .. in ..' with task sequences in F# tasks and async, #75, #93 and #99 (with help from @theangrybyrd) - - adds TaskSeq.singleton, #90 (by @gusty) - - improves TaskSeq.empty by not relying on resumable state, #89 (by @gusty) - - does not throw exceptions anymore for unequal lengths in TaskSeq.zip, fixes #32 + - adds support for 'for .. in ..' with task sequences in F# tasks and async, #75, #93 and #99 (with help from @theangrybyrd). + - adds TaskSeq.singleton, #90 (by @gusty). + - fixes overload resolution bug with 'use' and 'use!', #97 (thanks @peterfaria). + - improves TaskSeq.empty by not relying on resumable state, #89 (by @gusty). + - does not throw exceptions anymore for unequal lengths in TaskSeq.zip, fixes #32. 0.2.2 - removes TaskSeq.toSeqCachedAsync, which was incorrectly named. Use toSeq or toListAsync instead. - renames TaskSeq.toSeqCached to TaskSeq.toSeq, which was its actual operational behavior. From a488161cfbc132d3c6f6018bb17468513eabb86b Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Sun, 27 Nov 2022 01:47:52 +0100 Subject: [PATCH 54/68] Give `IAsyncDisposable` a higher precedence than `IDisposable`, part of #97 --- .../TaskSeq.Using.Tests.fs | 8 ++-- src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs | 43 ++++++++----------- 2 files changed, 23 insertions(+), 28 deletions(-) diff --git a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Using.Tests.fs b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Using.Tests.fs index fc5a62c7..07c7c247 100644 --- a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Using.Tests.fs +++ b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Using.Tests.fs @@ -26,10 +26,10 @@ type private MultiDispose(disposed: int ref) = inherit OneGetter() interface IDisposable with - member _.Dispose() = disposed.Value <- !disposed + 1 + member _.Dispose() = disposed.Value <- 1 interface IAsyncDisposable with - member _.DisposeAsync() = ValueTask(task { do disposed.Value <- !disposed + 1 }) + member _.DisposeAsync() = ValueTask(task { do disposed.Value <- -1 }) let private check = TaskSeq.length >> Task.map (should equal 1) @@ -67,7 +67,7 @@ let ``CE task: Using when type implements IDisposable and IAsyncDisposable`` () } check ts - |> Task.map (fun _ -> disposed.Value |> should equal 1) // only one of the two dispose method should fire + |> Task.map (fun _ -> disposed.Value |> should equal -1) // should prefer IAsyncDisposable, which returns -1 [] let ``CE task: Using! when type implements IDisposable`` () = @@ -103,4 +103,4 @@ let ``CE task: Using! when type implements IDisposable and IAsyncDisposable`` () } check ts - |> Task.map (fun _ -> disposed.Value |> should equal 1) // only one of the two dispose method should fire + |> Task.map (fun _ -> disposed.Value |> should equal -1) // should prefer IAsyncDisposable, which returns -1 diff --git a/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs b/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs index ab48de43..a372b14f 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs +++ b/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs @@ -482,26 +482,18 @@ type TaskSeqBuilder() = true) ) - member inline this.Using(disp: #IDisposable, body: #IDisposable -> TaskSeqCode<'T>) : TaskSeqCode<'T> = + member inline this.Using(disp: #IAsyncDisposable, body: #IAsyncDisposable -> TaskSeqCode<'T>) : TaskSeqCode<'T> = // A using statement is just a try/finally with the finally block disposing if non-null. - this.TryFinally( + this.TryFinallyAsync( (fun sm -> (body disp).Invoke(&sm)), (fun () -> - // yes, this can be null from time to time if not (isNull (box disp)) then - disp.Dispose()) - ) - - member inline this.For(sequence: seq<'TElement>, body: 'TElement -> TaskSeqCode<'T>) : TaskSeqCode<'T> = - // A for loop is just a using statement on the sequence's enumerator... - this.Using( - sequence.GetEnumerator(), - // ... and its body is a while loop that advances the enumerator and runs the body on each element. - (fun e -> this.While((fun () -> e.MoveNext()), (fun sm -> (body e.Current).Invoke(&sm)))) + disp.DisposeAsync().AsTask() + else + Task.CompletedTask) ) - member inline _.Yield(v: 'T) : TaskSeqCode<'T> = TaskSeqCode<'T>(fun sm -> // This will yield with __stack_fin = false @@ -513,8 +505,6 @@ type TaskSeqBuilder() = sm.Data.awaiter <- null __stack_fin) - member inline this.YieldFrom(source: seq<'T>) : TaskSeqCode<'T> = this.For(source, (fun v -> this.Yield(v))) - member inline _.Bind(task: Task<'TResult1>, continuation: ('TResult1 -> TaskSeqCode<'T>)) : TaskSeqCode<'T> = TaskSeqCode<'T>(fun sm -> let mutable awaiter = task.GetAwaiter() @@ -581,22 +571,27 @@ type TaskSeqBuilder() = module MediumPriority = type TaskSeqBuilder with - member inline this.Using - ( - disp: #IAsyncDisposable, - body: #IAsyncDisposable -> TaskSeqCode<'T> - ) : TaskSeqCode<'T> = + member inline this.Using(disp: #IDisposable, body: #IDisposable -> TaskSeqCode<'T>) : TaskSeqCode<'T> = // A using statement is just a try/finally with the finally block disposing if non-null. - this.TryFinallyAsync( + this.TryFinally( (fun sm -> (body disp).Invoke(&sm)), (fun () -> + // yes, this can be null from time to time if not (isNull (box disp)) then - disp.DisposeAsync().AsTask() - else - Task.CompletedTask) + disp.Dispose()) ) + member inline this.For(sequence: seq<'TElement>, body: 'TElement -> TaskSeqCode<'T>) : TaskSeqCode<'T> = + // A for loop is just a using statement on the sequence's enumerator... + this.Using( + sequence.GetEnumerator(), + // ... and its body is a while loop that advances the enumerator and runs the body on each element. + (fun e -> this.While((fun () -> e.MoveNext()), (fun sm -> (body e.Current).Invoke(&sm)))) + ) + + member inline this.YieldFrom(source: seq<'T>) : TaskSeqCode<'T> = this.For(source, (fun v -> this.Yield(v))) + member inline this.For ( source: #IAsyncEnumerable<'TElement>, From 0399f2e5607583ea3a71c65a9a9dda7fa0ef8ecc Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Sun, 27 Nov 2022 03:46:05 +0100 Subject: [PATCH 55/68] Separate `release-notes` from `fsproj` file and remove `TaskSeq.ico` everywhere --- assets/TaskSeq.ico | Bin 179661 -> 0 bytes release-notes.txt | 39 ++++++++++++++++++ resources/TaskSeq.ico | Bin 179661 -> 0 bytes .../FSharp.Control.TaskSeq.Test.fsproj | 2 - src/FSharp.Control.TaskSeq.sln | 2 +- .../FSharp.Control.TaskSeq.fsproj | 39 +----------------- 6 files changed, 42 insertions(+), 40 deletions(-) delete mode 100644 assets/TaskSeq.ico create mode 100644 release-notes.txt delete mode 100644 resources/TaskSeq.ico diff --git a/assets/TaskSeq.ico b/assets/TaskSeq.ico deleted file mode 100644 index 65d3ee0aff6faf1af94180d52627c31065b1b8f0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 179661 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 Tehbe?;TiYBGoO>!GM4@SruWSf diff --git a/release-notes.txt b/release-notes.txt new file mode 100644 index 00000000..7df065f1 --- /dev/null +++ b/release-notes.txt @@ -0,0 +1,39 @@ + +Release notes: + +0.3.0 (unreleased) + - adds support for 'for .. in ..' with task sequences in F# tasks and async, #75, #93 and #99 (with help from @theangrybyrd). + - adds TaskSeq.singleton, #90 (by @gusty). + - fixes overload resolution bug with 'use' and 'use!', #97 (thanks @peterfaria). + - improves TaskSeq.empty by not relying on resumable state, #89 (by @gusty). + - does not throw exceptions anymore for unequal lengths in TaskSeq.zip, fixes #32. + +0.2.2 + - removes TaskSeq.toSeqCachedAsync, which was incorrectly named. Use toSeq or toListAsync instead. + - renames TaskSeq.toSeqCached to TaskSeq.toSeq, which was its actual operational behavior. + +0.2.1 + - fixes an issue with ValueTask on completed iterations. + - adds `TaskSeq.except` and `TaskSeq.exceptOfSeq` async set operations. + +0.2 + - moved from NET 6.0, to NetStandard 2.1 for greater compatibility, no functional changes. + - move to minimally necessary FSharp.Core version: 6.0.2. + - updated readme with progress overview, corrected meta info, added release notes. + +0.1.1 + - updated meta info in nuget package and added readme. + +0.1 + - initial release + - implements taskSeq CE using resumable state machines + - with support for: yield, yield!, let, let!, while, for, try-with, try-finally, use, use! + - and: tasks and valuetasks + - adds toXXX / ofXXX functions + - adds map/mapi/fold/iter/iteri/collect etc with async variants + - adds find/pick/choose/filter etc with async variants and 'try' variants + - adds cast/concat/append/prepend/delay/exactlyOne + - adds empty/isEmpty + - adds findIndex/indexed/init/initInfinite + - adds head/last/tryHead/tryLast/tail/tryTail + - adds zip/length \ No newline at end of file diff --git a/resources/TaskSeq.ico b/resources/TaskSeq.ico deleted file mode 100644 index 65d3ee0aff6faf1af94180d52627c31065b1b8f0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 179661 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 Tehbe?;TiYBGoO>!GM4@SruWSf diff --git a/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj b/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj index 5a5d0df0..2fafcf30 100644 --- a/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj +++ b/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj @@ -5,11 +5,9 @@ false false - ..\..\assets\TaskSeq.ico - diff --git a/src/FSharp.Control.TaskSeq.sln b/src/FSharp.Control.TaskSeq.sln index f3413592..cce9e711 100644 --- a/src/FSharp.Control.TaskSeq.sln +++ b/src/FSharp.Control.TaskSeq.sln @@ -12,6 +12,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ..\build.cmd = ..\build.cmd ..\Directory.Build.props = ..\Directory.Build.props ..\README.md = ..\README.md + ..\release-notes.txt = ..\release-notes.txt ..\Version.props = ..\Version.props EndProjectSection EndProject @@ -31,7 +32,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "assets", "assets", "{B198D5 ProjectSection(SolutionItems) = preProject ..\assets\nuget-package-readme.md = ..\assets\nuget-package-readme.md ..\assets\taskseq-icon.png = ..\assets\taskseq-icon.png - ..\assets\TaskSeq.ico = ..\assets\TaskSeq.ico EndProjectSection EndProject Global diff --git a/src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj b/src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj index c276bfce..2564a319 100644 --- a/src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj +++ b/src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj @@ -3,7 +3,6 @@ netstandard2.1 true - ..\..\assets\TaskSeq.ico True Computation expression 'taskSeq' for processing IAsyncEnumerable sequences and module functions $(Version) @@ -21,48 +20,14 @@ Generates optimized IL code through the new resumable state machines, and comes MIT False nuget-package-readme.md - - Release notes: - 0.3.0 (unreleased) - - adds support for 'for .. in ..' with task sequences in F# tasks and async, #75, #93 and #99 (with help from @theangrybyrd). - - adds TaskSeq.singleton, #90 (by @gusty). - - fixes overload resolution bug with 'use' and 'use!', #97 (thanks @peterfaria). - - improves TaskSeq.empty by not relying on resumable state, #89 (by @gusty). - - does not throw exceptions anymore for unequal lengths in TaskSeq.zip, fixes #32. - 0.2.2 - - removes TaskSeq.toSeqCachedAsync, which was incorrectly named. Use toSeq or toListAsync instead. - - renames TaskSeq.toSeqCached to TaskSeq.toSeq, which was its actual operational behavior. - 0.2.1 - - fixes an issue with ValueTask on completed iterations. - - adds `TaskSeq.except` and `TaskSeq.exceptOfSeq` async set operations. - 0.2 - - moved from NET 6.0, to NetStandard 2.1 for greater compatibility, no functional changes. - - move to minimally necessary FSharp.Core version: 6.0.2. - - updated readme with progress overview, corrected meta info, added release notes. - 0.1.1 - - updated meta info in nuget package and added readme. - 0.1 - - initial release - - implements taskSeq CE using resumable state machines - - with support for: yield, yield!, let, let!, while, for, try-with, try-finally, use, use! - - and: tasks and valuetasks - - adds toXXX / ofXXX functions - - adds map/mapi/fold/iter/iteri/collect etc with async variants - - adds find/pick/choose/filter etc with async variants and 'try' variants - - adds cast/concat/append/prepend/delay/exactlyOne - - adds empty/isEmpty - - adds findIndex/indexed/init/initInfinite - - adds head/last/tryHead/tryLast/tail/tryTail - - adds zip/length - - + $([System.IO.File]::ReadAllText("$(MSBuildProjectDirectory)/../../release-notes.txt")) taskseq'fsharp;f#;computation expression;IAsyncEnumerable;task;async;asyncseq; True snupkg - + True \ From 639e2a61fd1a161f75634a1fe05c0e296d74b581 Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Sun, 27 Nov 2022 04:26:02 +0100 Subject: [PATCH 56/68] Add tests for `do` and `do!` and cleanup some helper functions --- .../FSharp.Control.TaskSeq.Test.fsproj | 1 + .../TaskSeq.Do.Tests.fs | 48 +++++++++++++++++++ .../TaskSeq.Using.Tests.fs | 14 +++--- src/FSharp.Control.TaskSeq.Test/TestUtils.fs | 2 + src/FSharp.Control.TaskSeq/Utils.fs | 18 ++++--- 5 files changed, 69 insertions(+), 14 deletions(-) create mode 100644 src/FSharp.Control.TaskSeq.Test/TaskSeq.Do.Tests.fs diff --git a/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj b/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj index 2fafcf30..c1fad98c 100644 --- a/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj +++ b/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj @@ -48,6 +48,7 @@ + diff --git a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Do.Tests.fs b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Do.Tests.fs new file mode 100644 index 00000000..3806fdeb --- /dev/null +++ b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Do.Tests.fs @@ -0,0 +1,48 @@ +module TaskSeq.Tests.Do + +open System +open System.Threading.Tasks +open FsUnit +open Xunit + +open FSharp.Control + +[] +let ``CE taskSeq: use 'do'`` () = + let mutable value = 0 + + taskSeq { do value <- value + 1 } + + |> verifyEmpty + +[] +let ``CE taskSeq: use 'do!' with a task`` () = + let mutable value = 0 + + taskSeq { do! task { do value <- value + 1 } } + + |> verifyEmpty + +//[] +//let ``CE taskSeq: use 'do!' with a valuetask`` () = +// let mutable value = 0 + +// taskSeq { do! ValueTask.ofIValueTaskSource (task { do value <- value + 1 }) } + +// |> verifyEmpty + +//[] +//let ``CE taskSeq: use 'do!' with a non-generic valuetask`` () = +// let mutable value = 0 + +// taskSeq { do! ValueTask(task { do value <- value + 1 }) } + +// |> verifyEmpty + +//[] +//let ``CE taskSeq: use 'do!' with a non-generic task`` () = +// let mutable value = 0 + +// taskSeq { do! (task { do value <- value + 1 }) |> Task.ignore } + +// |> verifyEmpty diff --git a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Using.Tests.fs b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Using.Tests.fs index 07c7c247..8ef92f5d 100644 --- a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Using.Tests.fs +++ b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Using.Tests.fs @@ -1,4 +1,4 @@ -module FSharp.Control.TaskSeq.Test +module TaskSeq.Test.Using open System open System.Threading.Tasks @@ -34,7 +34,7 @@ type private MultiDispose(disposed: int ref) = let private check = TaskSeq.length >> Task.map (should equal 1) [] -let ``CE task: Using when type implements IDisposable`` () = +let ``CE taskSeq: Using when type implements IDisposable`` () = let disposed = ref false let ts = taskSeq { @@ -46,7 +46,7 @@ let ``CE task: Using when type implements IDisposable`` () = |> Task.map (fun _ -> disposed.Value |> should be True) [] -let ``CE task: Using when type implements IAsyncDisposable`` () = +let ``CE taskSeq: Using when type implements IAsyncDisposable`` () = let disposed = ref false let ts = taskSeq { @@ -58,7 +58,7 @@ let ``CE task: Using when type implements IAsyncDisposable`` () = |> Task.map (fun _ -> disposed.Value |> should be True) [] -let ``CE task: Using when type implements IDisposable and IAsyncDisposable`` () = +let ``CE taskSeq: Using when type implements IDisposable and IAsyncDisposable`` () = let disposed = ref 0 let ts = taskSeq { @@ -70,7 +70,7 @@ let ``CE task: Using when type implements IDisposable and IAsyncDisposable`` () |> Task.map (fun _ -> disposed.Value |> should equal -1) // should prefer IAsyncDisposable, which returns -1 [] -let ``CE task: Using! when type implements IDisposable`` () = +let ``CE taskSeq: Using! when type implements IDisposable`` () = let disposed = ref false let ts = taskSeq { @@ -82,7 +82,7 @@ let ``CE task: Using! when type implements IDisposable`` () = |> Task.map (fun _ -> disposed.Value |> should be True) [] -let ``CE task: Using! when type implements IAsyncDisposable`` () = +let ``CE taskSeq: Using! when type implements IAsyncDisposable`` () = let disposed = ref false let ts = taskSeq { @@ -94,7 +94,7 @@ let ``CE task: Using! when type implements IAsyncDisposable`` () = |> Task.map (fun _ -> disposed.Value |> should be True) [] -let ``CE task: Using! when type implements IDisposable and IAsyncDisposable`` () = +let ``CE taskSeq: Using! when type implements IDisposable and IAsyncDisposable`` () = let disposed = ref 0 let ts = taskSeq { diff --git a/src/FSharp.Control.TaskSeq.Test/TestUtils.fs b/src/FSharp.Control.TaskSeq.Test/TestUtils.fs index 28b36031..36244708 100644 --- a/src/FSharp.Control.TaskSeq.Test/TestUtils.fs +++ b/src/FSharp.Control.TaskSeq.Test/TestUtils.fs @@ -136,11 +136,13 @@ type DummyTaskFactory(µsecMin: int64<µs>, µsecMax: int64<µs>) = [] module TestUtils = + /// Verifies that a task sequence is empty by converting to an array and checking emptiness. let verifyEmpty ts = ts |> TaskSeq.toArrayAsync |> Task.map (Array.isEmpty >> should be True) + /// Verifies that a task sequence contains integers 1-10, by converting to an array and comparing. let verify1To10 ts = ts |> TaskSeq.toArrayAsync diff --git a/src/FSharp.Control.TaskSeq/Utils.fs b/src/FSharp.Control.TaskSeq/Utils.fs index 15fce9b2..3cd2d350 100644 --- a/src/FSharp.Control.TaskSeq/Utils.fs +++ b/src/FSharp.Control.TaskSeq/Utils.fs @@ -28,6 +28,16 @@ module ValueTask = /// Creates a ValueTask with an IValueTaskSource representing the operation let inline ofIValueTaskSource taskSource version = ValueTask(taskSource, version) + /// Creates a ValueTask form a Task<'T> + let inline ofTask (task: Task<'T>) = ValueTask<'T>(task) + + /// Ignore a ValueTask<'T>, returns a non-generic ValueTask. + let inline ignore (vtask: ValueTask<'T>) = + if vtask.IsCompleted then + ValueTask() + else + ValueTask(vtask.AsTask()) + module Task = /// Convert an Async<'T> into a Task<'T> let inline ofAsync (async: Async<'T>) = task { return! async } @@ -41,22 +51,16 @@ module Task = /// Convert a Task<'T> into an Async<'T> let inline toAsync (task: Task<'T>) = Async.AwaitTask task - /// Convert a Task into a Task - let inline toTask (task: Task) = task :> Task - /// Convert a Task<'T> into a ValueTask<'T> let inline toValueTask (task: Task<'T>) = ValueTask<'T> task - /// Convert a Task into a non-generic ValueTask - let inline toIgnoreValueTask (task: Task) = ValueTask(task :> Task) - /// /// Convert a ValueTask<'T> to a Task<'T>. To use a non-generic ValueTask, /// consider using: . /// let inline ofValueTask (valueTask: ValueTask<'T>) = task { return! valueTask } - /// Convert a Task<'T> into a Task, ignoring the result + /// Convert a Task<'T> into a non-generic Task, ignoring the result let inline ignore (task: Task<'T>) = TaskBuilder.task { let! _ = task From 5173bbdb70112850e12cd310e5cf874c6817d73e Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Sun, 27 Nov 2022 05:20:53 +0100 Subject: [PATCH 57/68] Implement `^TaskLike` version of `Bind` to allow more flexible types with `do!`, like `ValueTask` and non-generic `Task` --- .../TaskSeq.Do.Tests.fs | 42 +++-- src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs | 161 ++++++++++++------ 2 files changed, 131 insertions(+), 72 deletions(-) diff --git a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Do.Tests.fs b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Do.Tests.fs index 3806fdeb..70400689 100644 --- a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Do.Tests.fs +++ b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Do.Tests.fs @@ -11,38 +11,36 @@ open FSharp.Control let ``CE taskSeq: use 'do'`` () = let mutable value = 0 - taskSeq { do value <- value + 1 } - - |> verifyEmpty + taskSeq { do value <- value + 1 } |> verifyEmpty [] let ``CE taskSeq: use 'do!' with a task`` () = let mutable value = 0 taskSeq { do! task { do value <- value + 1 } } - |> verifyEmpty + |> Task.map (fun _ -> value |> should equal 1) -//[] -//let ``CE taskSeq: use 'do!' with a valuetask`` () = -// let mutable value = 0 - -// taskSeq { do! ValueTask.ofIValueTaskSource (task { do value <- value + 1 }) } - -// |> verifyEmpty - -//[] -//let ``CE taskSeq: use 'do!' with a non-generic valuetask`` () = -// let mutable value = 0 +[] +let ``CE taskSeq: use 'do!' with a valuetask`` () = + let mutable value = 0 -// taskSeq { do! ValueTask(task { do value <- value + 1 }) } + taskSeq { do! ValueTask.ofTask (task { do value <- value + 1 }) } + |> verifyEmpty + |> Task.map (fun _ -> value |> should equal 1) -// |> verifyEmpty +[] +let ``CE taskSeq: use 'do!' with a non-generic valuetask`` () = + let mutable value = 0 -//[] -//let ``CE taskSeq: use 'do!' with a non-generic task`` () = -// let mutable value = 0 + taskSeq { do! ValueTask(task { do value <- value + 1 }) } + |> verifyEmpty + |> Task.map (fun _ -> value |> should equal 1) -// taskSeq { do! (task { do value <- value + 1 }) |> Task.ignore } +[] +let ``CE taskSeq: use 'do!' with a non-generic task`` () = + let mutable value = 0 -// |> verifyEmpty + taskSeq { do! (task { do value <- value + 1 }) |> Task.ignore } + |> verifyEmpty + |> Task.map (fun _ -> value |> should equal 1) diff --git a/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs b/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs index a372b14f..688d7d9d 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs +++ b/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs @@ -505,56 +505,6 @@ type TaskSeqBuilder() = sm.Data.awaiter <- null __stack_fin) - member inline _.Bind(task: Task<'TResult1>, continuation: ('TResult1 -> TaskSeqCode<'T>)) : TaskSeqCode<'T> = - TaskSeqCode<'T>(fun sm -> - let mutable awaiter = task.GetAwaiter() - let mutable __stack_fin = true - - Debug.logInfo "at Bind" - - if not awaiter.IsCompleted then - // This will yield with __stack_fin2 = false - // This will resume with __stack_fin2 = true - let __stack_fin2 = ResumableCode.Yield().Invoke(&sm) - __stack_fin <- __stack_fin2 - - Debug.logInfo ("at Bind: with __stack_fin = ", __stack_fin) - Debug.logInfo ("at Bind: this.completed = ", sm.Data.completed) - - if __stack_fin then - let result = awaiter.GetResult() - (continuation result).Invoke(&sm) - - else - Debug.logInfo "at Bind: calling AwaitUnsafeOnCompleted" - - sm.Data.awaiter <- awaiter - sm.Data.current <- ValueNone - false) - - member inline _.Bind(task: ValueTask<'TResult1>, continuation: ('TResult1 -> TaskSeqCode<'T>)) : TaskSeqCode<'T> = - TaskSeqCode<'T>(fun sm -> - let mutable awaiter = task.GetAwaiter() - let mutable __stack_fin = true - - Debug.logInfo "at BindV" - - if not awaiter.IsCompleted then - // This will yield with __stack_fin2 = false - // This will resume with __stack_fin2 = true - let __stack_fin2 = ResumableCode.Yield().Invoke(&sm) - __stack_fin <- __stack_fin2 - - if __stack_fin then - let result = awaiter.GetResult() - (continuation result).Invoke(&sm) - else - Debug.logInfo "at BindV: calling AwaitUnsafeOnCompleted" - - sm.Data.awaiter <- awaiter - sm.Data.current <- ValueNone - false) - // // These "modules of priority" allow for an indecisive F# to resolve // the proper overload if a single type implements more than one @@ -567,6 +517,58 @@ type TaskSeqBuilder() = // (like For depending on Using etc). // +[] +module LowPriority = + type TaskSeqBuilder with + + // + // Note: we cannot place _.Bind directly on the type, as the NoEagerXXX attribute + // has no effect, and each use of `do!` will give an overload error (because the + // `TaskLike` type and the `Task<_>` type are interchangeable). + // + // However, we cannot unify these two methods, because Task<_> inherits from Task (non-generic) + // and we need a way to distinguish these two methods. + // + + [] + member inline _.Bind< ^TaskLike, 'TResult1, 'TResult2, ^Awaiter, 'TOverall + when ^TaskLike: (member GetAwaiter: unit -> ^Awaiter) + and ^Awaiter :> ICriticalNotifyCompletion + and ^Awaiter: (member get_IsCompleted: unit -> bool) + and ^Awaiter: (member GetResult: unit -> 'TResult1)> + ( + task: ^TaskLike, + continuation: ('TResult1 -> TaskSeqCode<'TResult2>) + ) : TaskSeqCode<'TResult2> = + + TaskSeqCode<'TResult2>(fun sm -> + let mutable awaiter = (^TaskLike: (member GetAwaiter: unit -> ^Awaiter) (task)) + let mutable __stack_fin = true + + Debug.logInfo "at TaskLike bind!" + + if not (^Awaiter: (member get_IsCompleted: unit -> bool) (awaiter)) then + // This will yield with __stack_fin2 = false + // This will resume with __stack_fin2 = true + let __stack_fin2 = ResumableCode.Yield().Invoke(&sm) + __stack_fin <- __stack_fin2 + + Debug.logInfo ("at TaskLike bind!: with __stack_fin = ", __stack_fin) + Debug.logInfo ("at TaskLike bind!: this.completed = ", sm.Data.completed) + + if __stack_fin then + Debug.logInfo "at TaskLike bind!: finished awaiting, calling continuation" + let result = (^Awaiter: (member GetResult: unit -> 'TResult1) (awaiter)) + (continuation result).Invoke(&sm) + + else + Debug.logInfo "at TaskLike bind!: await further" + + sm.Data.awaiter <- awaiter + sm.Data.current <- ValueNone + false) + + [] module MediumPriority = type TaskSeqBuilder with @@ -608,3 +610,62 @@ module MediumPriority = member inline this.YieldFrom(source: IAsyncEnumerable<'T>) : TaskSeqCode<'T> = this.For(source, (fun v -> this.Yield(v))) + +[] +module HighPriority = + type TaskSeqBuilder with + + member inline _.Bind(task: Task<'TResult1>, continuation: ('TResult1 -> TaskSeqCode<'T>)) : TaskSeqCode<'T> = + TaskSeqCode<'T>(fun sm -> + let mutable awaiter = task.GetAwaiter() + let mutable __stack_fin = true + + Debug.logInfo "at Bind" + + if not awaiter.IsCompleted then + // This will yield with __stack_fin2 = false + // This will resume with __stack_fin2 = true + let __stack_fin2 = ResumableCode.Yield().Invoke(&sm) + __stack_fin <- __stack_fin2 + + Debug.logInfo ("at Bind: with __stack_fin = ", __stack_fin) + Debug.logInfo ("at Bind: this.completed = ", sm.Data.completed) + + if __stack_fin then + Debug.logInfo "at Bind: finished awaiting, calling continuation" + let result = awaiter.GetResult() + (continuation result).Invoke(&sm) + + else + Debug.logInfo "at Bind: await further" + + sm.Data.awaiter <- awaiter + sm.Data.current <- ValueNone + false) + + member inline _.Bind + ( + task: ValueTask<'TResult1>, + continuation: ('TResult1 -> TaskSeqCode<'T>) + ) : TaskSeqCode<'T> = + TaskSeqCode<'T>(fun sm -> + let mutable awaiter = task.GetAwaiter() + let mutable __stack_fin = true + + Debug.logInfo "at BindV" + + if not awaiter.IsCompleted then + // This will yield with __stack_fin2 = false + // This will resume with __stack_fin2 = true + let __stack_fin2 = ResumableCode.Yield().Invoke(&sm) + __stack_fin <- __stack_fin2 + + if __stack_fin then + let result = awaiter.GetResult() + (continuation result).Invoke(&sm) + else + Debug.logInfo "at BindV: calling AwaitUnsafeOnCompleted" + + sm.Data.awaiter <- awaiter + sm.Data.current <- ValueNone + false) From c1b2c4163f20d80c611874707d8a6b9765c473d0 Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Sun, 27 Nov 2022 17:21:31 +0100 Subject: [PATCH 58/68] Remove `ValueTask`, now handled by `^TaskLike`, see comments in code --- .../FSharp.Control.TaskSeq.Test.fsproj | 1 + src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs | 57 ++++++++----------- 2 files changed, 25 insertions(+), 33 deletions(-) diff --git a/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj b/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj index c1fad98c..611663b8 100644 --- a/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj +++ b/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj @@ -49,6 +49,7 @@ + diff --git a/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs b/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs index 688d7d9d..2fc1a426 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs +++ b/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs @@ -524,11 +524,19 @@ module LowPriority = // // Note: we cannot place _.Bind directly on the type, as the NoEagerXXX attribute // has no effect, and each use of `do!` will give an overload error (because the - // `TaskLike` type and the `Task<_>` type are interchangeable). + // `TaskLike` type and the `Task<_>` type are partially interchangeable, see notes there). // // However, we cannot unify these two methods, because Task<_> inherits from Task (non-generic) // and we need a way to distinguish these two methods. // + // Types handled: + // - ValueTask (non-generic, because it implements GetResult() -> unit) + // - ValueTask<'T> (because it implements GetResult() -> 'TResult) + // - Task (non-generic, because it implements GetResult() -> unit) + // - any other type that implements GetAwaiter() + // + // Not handled: + // - Task<'T> (because it only implements GetResult() -> unit, not GetResult() -> 'TResult) [] member inline _.Bind< ^TaskLike, 'TResult1, 'TResult2, ^Awaiter, 'TOverall @@ -545,7 +553,7 @@ module LowPriority = let mutable awaiter = (^TaskLike: (member GetAwaiter: unit -> ^Awaiter) (task)) let mutable __stack_fin = true - Debug.logInfo "at TaskLike bind!" + Debug.logInfo "at TaskLike bind" if not (^Awaiter: (member get_IsCompleted: unit -> bool) (awaiter)) then // This will yield with __stack_fin2 = false @@ -553,8 +561,8 @@ module LowPriority = let __stack_fin2 = ResumableCode.Yield().Invoke(&sm) __stack_fin <- __stack_fin2 - Debug.logInfo ("at TaskLike bind!: with __stack_fin = ", __stack_fin) - Debug.logInfo ("at TaskLike bind!: this.completed = ", sm.Data.completed) + Debug.logInfo ("at TaskLike bind: with __stack_fin = ", __stack_fin) + Debug.logInfo ("at TaskLike bind: this.completed = ", sm.Data.completed) if __stack_fin then Debug.logInfo "at TaskLike bind!: finished awaiting, calling continuation" @@ -562,7 +570,7 @@ module LowPriority = (continuation result).Invoke(&sm) else - Debug.logInfo "at TaskLike bind!: await further" + Debug.logInfo "at TaskLike bind: await further" sm.Data.awaiter <- awaiter sm.Data.current <- ValueNone @@ -615,9 +623,19 @@ module MediumPriority = module HighPriority = type TaskSeqBuilder with + // + // Notes Task: + // - Task<_> implements GetAwaiter(), but TaskAwaiter does not implement GetResult() -> TResult + // - Instead, it has GetResult() -> unit, which is not '^TaskLike' + // - Conclusion: we need an extra high-prio overload to allow support for Task<_> + // + // Notes ValueTask: + // - In contrast, ValueTask<_> *does have* GetResult() -> 'TResult + // - Conclusion: we do not need an extra overload anymore for ValueTask + // member inline _.Bind(task: Task<'TResult1>, continuation: ('TResult1 -> TaskSeqCode<'T>)) : TaskSeqCode<'T> = TaskSeqCode<'T>(fun sm -> - let mutable awaiter = task.GetAwaiter() + let mutable awaiter: TaskAwaiter<'TResult1> = task.GetAwaiter() let mutable __stack_fin = true Debug.logInfo "at Bind" @@ -642,30 +660,3 @@ module HighPriority = sm.Data.awaiter <- awaiter sm.Data.current <- ValueNone false) - - member inline _.Bind - ( - task: ValueTask<'TResult1>, - continuation: ('TResult1 -> TaskSeqCode<'T>) - ) : TaskSeqCode<'T> = - TaskSeqCode<'T>(fun sm -> - let mutable awaiter = task.GetAwaiter() - let mutable __stack_fin = true - - Debug.logInfo "at BindV" - - if not awaiter.IsCompleted then - // This will yield with __stack_fin2 = false - // This will resume with __stack_fin2 = true - let __stack_fin2 = ResumableCode.Yield().Invoke(&sm) - __stack_fin <- __stack_fin2 - - if __stack_fin then - let result = awaiter.GetResult() - (continuation result).Invoke(&sm) - else - Debug.logInfo "at BindV: calling AwaitUnsafeOnCompleted" - - sm.Data.awaiter <- awaiter - sm.Data.current <- ValueNone - false) From 1e643ebff1fc0af4608946e0a69cf13002d9b70d Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Sun, 27 Nov 2022 17:22:07 +0100 Subject: [PATCH 59/68] Add let-bang tests for corner cases --- .../TaskSeq.Let.Tests.fs | 82 +++++++++++++++++++ src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs | 2 +- 2 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 src/FSharp.Control.TaskSeq.Test/TaskSeq.Let.Tests.fs diff --git a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Let.Tests.fs b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Let.Tests.fs new file mode 100644 index 00000000..a4b9b66d --- /dev/null +++ b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Let.Tests.fs @@ -0,0 +1,82 @@ +module TaskSeq.Tests.Let + +open System +open System.Threading.Tasks +open FsUnit +open Xunit + +open FSharp.Control + +[] +let ``CE taskSeq: use 'let'`` () = + let mutable value = 0 + + taskSeq { + let value1 = value + 1 + let value2 = value1 + 1 + yield value2 + } + |> TaskSeq.exactlyOne + |> Task.map (should equal 2) + +[] +let ``CE taskSeq: use 'let!' with a task`` () = + let mutable value = 0 + + taskSeq { + let! unit' = task { do value <- value + 1 } + do unit' + } + |> verifyEmpty + |> Task.map (fun _ -> value |> should equal 1) + +[] +let ``CE taskSeq: use 'let!' with a task`` () = + taskSeq { + let! test = task { return "test" } + yield test + } + |> TaskSeq.exactlyOne + |> Task.map (should equal "test") + +[] +let ``CE taskSeq: use 'let!' with a valuetask`` () = + let mutable value = 0 + + taskSeq { + let! unit' = ValueTask.ofTask (task { do value <- value + 1 }) + do unit' + } + |> verifyEmpty + |> Task.map (fun _ -> value |> should equal 1) + +[] +let ``CE taskSeq: use 'let!' with a valuetask`` () = + taskSeq { + let! test = ValueTask.ofTask (task { return "test" }) + yield test + } + |> TaskSeq.exactlyOne + |> Task.map (should equal "test") + +[] +let ``CE taskSeq: use 'let!' with a non-generic valuetask`` () = + let mutable value = 0 + + taskSeq { + let! unit' = ValueTask(task { do value <- value + 1 }) + do unit' + } + |> verifyEmpty + |> Task.map (fun _ -> value |> should equal 1) + +[] +let ``CE taskSeq: use 'let!' with a non-generic task`` () = + let mutable value = 0 + + taskSeq { + let! unit' = (task { do value <- value + 1 }) |> Task.ignore + do unit' + } + |> verifyEmpty + |> Task.map (fun _ -> value |> should equal 1) diff --git a/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs b/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs index 2fc1a426..ea154804 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs +++ b/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs @@ -635,7 +635,7 @@ module HighPriority = // member inline _.Bind(task: Task<'TResult1>, continuation: ('TResult1 -> TaskSeqCode<'T>)) : TaskSeqCode<'T> = TaskSeqCode<'T>(fun sm -> - let mutable awaiter: TaskAwaiter<'TResult1> = task.GetAwaiter() + let mutable awaiter = task.GetAwaiter() let mutable __stack_fin = true Debug.logInfo "at Bind" From 568f754350d6d315463e4d17228ff103dc94bba5 Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Sun, 27 Nov 2022 17:29:41 +0100 Subject: [PATCH 60/68] Add a do-bang test with `Task.Delay` from the issue description --- src/FSharp.Control.TaskSeq.Test/TaskSeq.Do.Tests.fs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Do.Tests.fs b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Do.Tests.fs index 70400689..d979f6fa 100644 --- a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Do.Tests.fs +++ b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Do.Tests.fs @@ -44,3 +44,15 @@ let ``CE taskSeq: use 'do!' with a non-generic task`` () = taskSeq { do! (task { do value <- value + 1 }) |> Task.ignore } |> verifyEmpty |> Task.map (fun _ -> value |> should equal 1) + +[] +let ``CE taskSeq: use 'do!' with a task-delay`` () = + let mutable value = 0 + + taskSeq { + do value <- value + 1 + do! Task.Delay 50 + do value <- value + 1 + } + |> verifyEmpty + |> Task.map (fun _ -> value |> should equal 2) From b291d2af492aa17b9756ae2c930cfc60cd4ed1f8 Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Sun, 27 Nov 2022 18:46:28 +0100 Subject: [PATCH 61/68] Update `release-notes.txt` for #109, #110 and #43 --- release-notes.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/release-notes.txt b/release-notes.txt index 7df065f1..8b5c0802 100644 --- a/release-notes.txt +++ b/release-notes.txt @@ -2,6 +2,8 @@ Release notes: 0.3.0 (unreleased) + - adds support for static TaskLike, allowing the same let! and do! overloads that F# task supports, fixes #110. + - implements 'do!' for non-generic Task like with Task.Delay, fixes #43. - adds support for 'for .. in ..' with task sequences in F# tasks and async, #75, #93 and #99 (with help from @theangrybyrd). - adds TaskSeq.singleton, #90 (by @gusty). - fixes overload resolution bug with 'use' and 'use!', #97 (thanks @peterfaria). From 5f1f9e4bcd52bcd58eaab50eaa51beced9b7b683 Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Mon, 28 Nov 2022 00:05:44 +0100 Subject: [PATCH 62/68] Fix `ValueTask.ignore` based on Stephen Toub's insights --- src/FSharp.Control.TaskSeq/Utils.fs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/FSharp.Control.TaskSeq/Utils.fs b/src/FSharp.Control.TaskSeq/Utils.fs index 3cd2d350..5b35fcaa 100644 --- a/src/FSharp.Control.TaskSeq/Utils.fs +++ b/src/FSharp.Control.TaskSeq/Utils.fs @@ -33,7 +33,11 @@ module ValueTask = /// Ignore a ValueTask<'T>, returns a non-generic ValueTask. let inline ignore (vtask: ValueTask<'T>) = - if vtask.IsCompleted then + // this implementation follows Stephen Toub's advice, see: + // https://github.com/dotnet/runtime/issues/31503#issuecomment-554415966 + if vtask.IsCompletedSuccessfully then + // ensure any side effect executes + vtask.Result |> ignore ValueTask() else ValueTask(vtask.AsTask()) @@ -63,6 +67,7 @@ module Task = /// Convert a Task<'T> into a non-generic Task, ignoring the result let inline ignore (task: Task<'T>) = TaskBuilder.task { + // ensure the task is awaited let! _ = task return () } From e080acd87708d572dbd204f343ef80987315ae8b Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Sun, 27 Nov 2022 20:51:37 +0100 Subject: [PATCH 63/68] Add signature files for `taskSeq` and builders, plus utils, hide debug stuff etc --- src/FSharp.Control.TaskSeq/AssemblyInfo.fs | 8 + src/FSharp.Control.TaskSeq/DebugUtils.fs | 55 ++ .../FSharp.Control.TaskSeq.fsproj | 13 + src/FSharp.Control.TaskSeq/TaskSeq.fsi | 888 +++++++----------- src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs | 7 +- src/FSharp.Control.TaskSeq/TaskSeqBuilder.fsi | 166 ++++ src/FSharp.Control.TaskSeq/TaskSeqInternal.fs | 17 +- src/FSharp.Control.TaskSeq/Utils.fs | 49 - src/FSharp.Control.TaskSeq/Utils.fsi | 87 ++ 9 files changed, 661 insertions(+), 629 deletions(-) create mode 100644 src/FSharp.Control.TaskSeq/AssemblyInfo.fs create mode 100644 src/FSharp.Control.TaskSeq/DebugUtils.fs create mode 100644 src/FSharp.Control.TaskSeq/TaskSeqBuilder.fsi create mode 100644 src/FSharp.Control.TaskSeq/Utils.fsi diff --git a/src/FSharp.Control.TaskSeq/AssemblyInfo.fs b/src/FSharp.Control.TaskSeq/AssemblyInfo.fs new file mode 100644 index 00000000..d8bda820 --- /dev/null +++ b/src/FSharp.Control.TaskSeq/AssemblyInfo.fs @@ -0,0 +1,8 @@ +namespace TaskSeq.Tests + +open System.Runtime.CompilerServices + +// ensure the test project has access to the internal types +[] + +do () diff --git a/src/FSharp.Control.TaskSeq/DebugUtils.fs b/src/FSharp.Control.TaskSeq/DebugUtils.fs new file mode 100644 index 00000000..aab74e3a --- /dev/null +++ b/src/FSharp.Control.TaskSeq/DebugUtils.fs @@ -0,0 +1,55 @@ +namespace FSharp.Control + +open System.Threading.Tasks +open System +open System.Diagnostics +open System.Threading + +type Debug = + + [] + static val mutable private verbose: bool option + + /// Setting from environment variable TASKSEQ_LOG_VERBOSE, which, + /// when set, enables (very) verbose printing of flow and state + static member private getVerboseSetting() = + match Debug.verbose with + | None -> + let verboseEnv = + try + match Environment.GetEnvironmentVariable "TASKSEQ_LOG_VERBOSE" with + | null -> false + | x -> + match x.ToLowerInvariant().Trim() with + | "1" + | "true" + | "on" + | "yes" -> true + | _ -> false + + with _ -> + false + + Debug.verbose <- Some verboseEnv + verboseEnv + + | Some setting -> setting + + /// Private helper to log to stdout in DEBUG builds only + [] + static member private print value = + match Debug.getVerboseSetting () with + | false -> () + | true -> + // don't use ksprintf here, because the compiler does not remove all allocations due to + // the way PrintfFormat types are compiled, even if we set the Conditional attribute. + let ct = Thread.CurrentThread + printfn "%i (%b): %s" ct.ManagedThreadId ct.IsThreadPoolThread value + + /// Log to stdout in DEBUG builds only + [] + static member logInfo(str) = Debug.print str + + /// Log to stdout in DEBUG builds only + [] + static member logInfo(str, data) = Debug.print $"%s{str}{data}" diff --git a/src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj b/src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj index 2564a319..7ec1202e 100644 --- a/src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj +++ b/src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj @@ -26,6 +26,15 @@ Generates optimized IL code through the new resumable state machines, and comes snupkg + + + + + + + True + + @@ -33,7 +42,11 @@ Generates optimized IL code through the new resumable state machines, and comes \ + + + + diff --git a/src/FSharp.Control.TaskSeq/TaskSeq.fsi b/src/FSharp.Control.TaskSeq/TaskSeq.fsi index 1f0f1497..47f1b1e4 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeq.fsi +++ b/src/FSharp.Control.TaskSeq/TaskSeq.fsi @@ -1,570 +1,322 @@ namespace FSharp.Control + + module TaskSeq = + + val empty<'T> : System.Collections.Generic.IAsyncEnumerable<'T> + + val singleton: + source: 'T -> System.Collections.Generic.IAsyncEnumerable<'T> + + val isEmpty: source: taskSeq<'T> -> System.Threading.Tasks.Task + + val toList: source: taskSeq<'T> -> 'T list + + val toArray: source: taskSeq<'T> -> 'T[] + + val toSeq: source: taskSeq<'T> -> seq<'T> + + val toArrayAsync: + source: taskSeq<'T> -> System.Threading.Tasks.Task<'T[]> + + val toListAsync: + source: taskSeq<'T> -> System.Threading.Tasks.Task<'T list> + + val toResizeArrayAsync: + source: taskSeq<'T> -> System.Threading.Tasks.Task> + + val toIListAsync: + source: taskSeq<'T> -> + System.Threading.Tasks.Task> + + val ofArray: + source: 'T[] -> System.Collections.Generic.IAsyncEnumerable<'T> + + val ofList: + source: 'T list -> System.Collections.Generic.IAsyncEnumerable<'T> + + val ofSeq: + source: seq<'T> -> System.Collections.Generic.IAsyncEnumerable<'T> + + val ofResizeArray: + source: ResizeArray<'T> -> + System.Collections.Generic.IAsyncEnumerable<'T> + + val ofTaskSeq: + source: seq<#System.Threading.Tasks.Task<'T>> -> + System.Collections.Generic.IAsyncEnumerable<'T> + + val ofTaskList: + source: #System.Threading.Tasks.Task<'T> list -> + System.Collections.Generic.IAsyncEnumerable<'T> + + val ofTaskArray: + source: #System.Threading.Tasks.Task<'T> array -> + System.Collections.Generic.IAsyncEnumerable<'T> + + val ofAsyncSeq: + source: seq> -> + System.Collections.Generic.IAsyncEnumerable<'T> + + val ofAsyncList: + source: Async<'T> list -> + System.Collections.Generic.IAsyncEnumerable<'T> + + val ofAsyncArray: + source: Async<'T> array -> + System.Collections.Generic.IAsyncEnumerable<'T> + + val length: source: taskSeq<'T> -> System.Threading.Tasks.Task + + val lengthOrMax: + max: int -> source: taskSeq<'T> -> System.Threading.Tasks.Task + + val lengthBy: + predicate: ('T -> bool) -> + source: taskSeq<'T> -> System.Threading.Tasks.Task + + val lengthByAsync: + predicate: ('T -> #System.Threading.Tasks.Task) -> + source: taskSeq<'T> -> System.Threading.Tasks.Task + + val init: + count: int -> + initializer: (int -> 'T) -> + System.Collections.Generic.IAsyncEnumerable<'T> + + val initInfinite: + initializer: (int -> 'T) -> + System.Collections.Generic.IAsyncEnumerable<'T> + + val initAsync: + count: int -> + initializer: (int -> #System.Threading.Tasks.Task<'T>) -> + System.Collections.Generic.IAsyncEnumerable<'T> + + val initInfiniteAsync: + initializer: (int -> #System.Threading.Tasks.Task<'T>) -> + System.Collections.Generic.IAsyncEnumerable<'T> + + val delay: + generator: (unit -> taskSeq<'T>) -> + System.Collections.Generic.IAsyncEnumerable<'T> + + val concat: + sources: taskSeq<#taskSeq<'T>> -> + System.Collections.Generic.IAsyncEnumerable<'T> + + val append: + source1: #taskSeq<'T> -> + source2: #taskSeq<'T> -> + System.Collections.Generic.IAsyncEnumerable<'T> + + val appendSeq: + source1: #taskSeq<'T> -> + source2: #seq<'T> -> System.Collections.Generic.IAsyncEnumerable<'T> + + val prependSeq: + source1: #seq<'T> -> + source2: #taskSeq<'T> -> + System.Collections.Generic.IAsyncEnumerable<'T> + + val cast: source: taskSeq -> taskSeq<'T> + + val box: + source: taskSeq<'T> -> + System.Collections.Generic.IAsyncEnumerable + + val unbox: source: taskSeq -> taskSeq<'U> when 'U: struct + + val iter: + action: ('T -> unit) -> + source: taskSeq<'T> -> System.Threading.Tasks.Task + + val iteri: + action: (int -> 'T -> unit) -> + source: taskSeq<'T> -> System.Threading.Tasks.Task + + val iterAsync: + action: ('T -> #System.Threading.Tasks.Task) -> + source: taskSeq<'T> -> System.Threading.Tasks.Task + + val iteriAsync: + action: (int -> 'T -> #System.Threading.Tasks.Task) -> + source: taskSeq<'T> -> System.Threading.Tasks.Task + + val map: + mapper: ('T -> 'U) -> + source: taskSeq<'T> -> + System.Collections.Generic.IAsyncEnumerable<'U> + + val mapi: + mapper: (int -> 'T -> 'U) -> + source: taskSeq<'T> -> + System.Collections.Generic.IAsyncEnumerable<'U> + + val mapAsync: + mapper: ('T -> #System.Threading.Tasks.Task<'U>) -> + source: taskSeq<'T> -> + System.Collections.Generic.IAsyncEnumerable<'U> + + val mapiAsync: + mapper: (int -> 'T -> #System.Threading.Tasks.Task<'U>) -> + source: taskSeq<'T> -> + System.Collections.Generic.IAsyncEnumerable<'U> + + val collect: + binder: ('T -> #System.Collections.Generic.IAsyncEnumerable<'U>) -> + source: taskSeq<'T> -> + System.Collections.Generic.IAsyncEnumerable<'U> + + val collectSeq: + binder: ('T -> #seq<'U>) -> + source: taskSeq<'T> -> + System.Collections.Generic.IAsyncEnumerable<'U> + + val collectAsync: + binder: ('T -> #System.Threading.Tasks.Task<'TSeqU>) -> + source: taskSeq<'T> -> taskSeq<'U> + when 'TSeqU :> System.Collections.Generic.IAsyncEnumerable<'U> + + val collectSeqAsync: + binder: ('T -> #System.Threading.Tasks.Task<'SeqU>) -> + source: taskSeq<'T> -> taskSeq<'U> when 'SeqU :> seq<'U> + + val tryHead: + source: taskSeq<'T> -> System.Threading.Tasks.Task<'T option> + + val head: source: taskSeq<'T> -> System.Threading.Tasks.Task<'T> + + val tryLast: + source: taskSeq<'T> -> System.Threading.Tasks.Task<'T option> + + val last: source: taskSeq<'T> -> System.Threading.Tasks.Task<'T> + + val tryTail: + source: taskSeq<'T> -> + System.Threading.Tasks.Task option> + + val tail: + source: taskSeq<'T> -> + System.Threading.Tasks.Task> + + val tryItem: + index: int -> + source: taskSeq<'T> -> System.Threading.Tasks.Task<'T option> + + val item: + index: int -> source: taskSeq<'T> -> System.Threading.Tasks.Task<'T> + + val tryExactlyOne: + source: taskSeq<'T> -> System.Threading.Tasks.Task<'T option> + + val exactlyOne: source: taskSeq<'T> -> System.Threading.Tasks.Task<'T> + + val indexed: + source: taskSeq<'T> -> + System.Collections.Generic.IAsyncEnumerable + + val choose: + chooser: ('T -> 'U option) -> + source: taskSeq<'T> -> + System.Collections.Generic.IAsyncEnumerable<'U> + + val chooseAsync: + chooser: ('T -> #System.Threading.Tasks.Task<'U option>) -> + source: taskSeq<'T> -> + System.Collections.Generic.IAsyncEnumerable<'U> + + val filter: + predicate: ('T -> bool) -> + source: taskSeq<'T> -> + System.Collections.Generic.IAsyncEnumerable<'T> + + val filterAsync: + predicate: ('T -> #System.Threading.Tasks.Task) -> + source: taskSeq<'T> -> + System.Collections.Generic.IAsyncEnumerable<'T> + + val tryPick: + chooser: ('T -> 'U option) -> + source: taskSeq<'T> -> System.Threading.Tasks.Task<'U option> + + val tryPickAsync: + chooser: ('T -> #System.Threading.Tasks.Task<'U option>) -> + source: taskSeq<'T> -> System.Threading.Tasks.Task<'U option> + + val tryFind: + predicate: ('T -> bool) -> + source: taskSeq<'T> -> System.Threading.Tasks.Task<'T option> + + val tryFindAsync: + predicate: ('T -> #System.Threading.Tasks.Task) -> + source: taskSeq<'T> -> System.Threading.Tasks.Task<'T option> + + val tryFindIndex: + predicate: ('T -> bool) -> + source: taskSeq<'T> -> System.Threading.Tasks.Task + + val tryFindIndexAsync: + predicate: ('T -> #System.Threading.Tasks.Task) -> + source: taskSeq<'T> -> System.Threading.Tasks.Task + + val except: + itemsToExclude: taskSeq<'T> -> + source: taskSeq<'T> -> + System.Collections.Generic.IAsyncEnumerable<'T> when 'T: equality + + val exceptOfSeq: + itemsToExclude: seq<'T> -> + source: taskSeq<'T> -> + System.Collections.Generic.IAsyncEnumerable<'T> when 'T: equality + + val exists: + predicate: ('T -> bool) -> + source: taskSeq<'T> -> System.Threading.Tasks.Task + + val existsAsync: + predicate: ('T -> #System.Threading.Tasks.Task) -> + source: taskSeq<'T> -> System.Threading.Tasks.Task + + val contains: + value: 'T -> source: taskSeq<'T> -> System.Threading.Tasks.Task + when 'T: equality + + val pick: + chooser: ('T -> 'U option) -> + source: taskSeq<'T> -> System.Threading.Tasks.Task<'U> + + val pickAsync: + chooser: ('T -> #System.Threading.Tasks.Task<'U option>) -> + source: taskSeq<'T> -> System.Threading.Tasks.Task<'U> + + val find: + predicate: ('T -> bool) -> + source: taskSeq<'T> -> System.Threading.Tasks.Task<'T> + + val findAsync: + predicate: ('T -> #System.Threading.Tasks.Task) -> + source: taskSeq<'T> -> System.Threading.Tasks.Task<'T> + + val findIndex: + predicate: ('T -> bool) -> + source: taskSeq<'T> -> System.Threading.Tasks.Task + + val findIndexAsync: + predicate: ('T -> #System.Threading.Tasks.Task) -> + source: taskSeq<'T> -> System.Threading.Tasks.Task + + val zip: + source1: taskSeq<'T> -> + source2: taskSeq<'U> -> + System.Collections.Generic.IAsyncEnumerable<'T * 'U> + + val fold: + folder: ('State -> 'T -> 'State) -> + state: 'State -> + source: taskSeq<'T> -> System.Threading.Tasks.Task<'State> + + val foldAsync: + folder: ('State -> 'T -> #System.Threading.Tasks.Task<'State>) -> + state: 'State -> + source: taskSeq<'T> -> System.Threading.Tasks.Task<'State> -#nowarn "1204" - -module TaskSeq = - open System.Collections.Generic - open System.Threading.Tasks - - /// Initialize an empty taskSeq. - val empty<'T> : taskSeq<'T> - - /// - /// Creates a sequence from that generates a single element and then ends. - /// - val singleton: source: 'T -> taskSeq<'T> - - /// - /// Returns if the task sequence contains no elements, otherwise. - /// - val isEmpty: source: taskSeq<'T> -> Task - - /// - /// Returns the length of the sequence. This operation requires the whole sequence to be evaluated and - /// should not be used on potentially infinite sequences, see for an alternative. - /// - val length: source: taskSeq<'T> -> Task - - /// - /// Returns the length of the sequence, or , whichever comes first. This operation requires the task sequence - /// to be evaluated in full, or until items have been processed. Use this method instead of - /// if you want to prevent too many items to be evaluated, or if the sequence is potentially infinite. - /// - val lengthOrMax: max: int -> source: taskSeq<'T> -> Task - - /// - /// Returns the length of the sequence of all items for which the returns true. - /// This operation requires the whole sequence to be evaluated and should not be used on potentially infinite sequences. - /// - val lengthBy: predicate: ('T -> bool) -> source: taskSeq<'T> -> Task - - /// - /// Returns the length of the sequence of all items for which the returns true. - /// This operation requires the whole sequence to be evaluated and should not be used on potentially infinite sequences. - /// If does not need to be asynchronous, consider using . - /// - val lengthByAsync: predicate: ('T -> #Task) -> source: taskSeq<'T> -> Task - - /// - /// Returns a task sequence that is given by the delayed specification of a task sequence. - /// - /// - /// The generating function for the task sequence. - /// The generated task sequence. - val delay: generator: (unit -> taskSeq<'T>) -> taskSeq<'T> - - /// - /// Generates a new task sequence which, when iterated, will return successive elements by calling the given function - /// with the current index, up to the given count. Each element is saved after its initialization for successive access to - /// , which will not re-evaluate the . However, - /// re-iterating the returned task sequence will re-evaluate the initialization function. The returned sequence may - /// be passed between threads safely. However, individual IEnumerator values generated from the returned sequence should - /// not be accessed concurrently. - /// - /// - /// The maximum number of items to generate for the sequence. - /// A function that generates an item in the sequence from a given index. - /// The resulting task sequence. - /// Thrown when count is negative. - val init: count: int -> initializer: (int -> 'T) -> taskSeq<'T> - - /// - /// Generates a new task sequence which, when iterated, will return successive elements by calling the given function - /// with the current index, up to the given count. Each element is saved after its initialization for successive access to - /// , which will not re-evaluate the . However, - /// re-iterating the returned task sequence will re-evaluate the initialization function. The returned sequence may - /// be passed between threads safely. However, individual IEnumerator values generated from the returned sequence should - /// not be accessed concurrently. - /// - /// - /// The maximum number of items to generate for the sequence. - /// A function that generates an item in the sequence from a given index. - /// The resulting task sequence. - /// Thrown when count is negative. - val initAsync: count: int -> initializer: (int -> #Task<'T>) -> taskSeq<'T> - - /// - /// Generates a new task sequence which, when iterated, will return successive elements by calling the given function - /// with the current index, ad infinitum, or until is reached. - /// Each element is saved after its initialization for successive access to - /// , which will not re-evaluate the . However, - /// re-iterating the returned task sequence will re-evaluate the initialization function. The returned sequence may - /// be passed between threads safely. However, individual IEnumerator values generated from the returned sequence should - /// not be accessed concurrently. - /// - /// - /// A function that generates an item in the sequence from a given index. - /// The resulting task sequence. - val initInfinite: initializer: (int -> 'T) -> taskSeq<'T> - - /// - /// Generates a new task sequence which, when iterated, will return successive elements by calling the given function - /// with the current index, ad infinitum, or until is reached. - /// Each element is saved after its initialization for successive access to - /// , which will not re-evaluate the . However, - /// re-iterating the returned task sequence will re-evaluate the initialization function. The returned sequence may - /// be passed between threads safely. However, individual IEnumerator values generated from the returned sequence should - /// not be accessed concurrently. - /// - /// - /// A function that generates an item in the sequence from a given index. - /// The resulting task sequence. - val initInfiniteAsync: initializer: (int -> #Task<'T>) -> taskSeq<'T> - - /// - /// Combines the given task sequence of task sequences and concatenates them end-to-end, to form a - /// new flattened, single task sequence. Each task sequence is awaited item by item, before the next is iterated. - /// - /// - /// The input enumeration-of-enumerations. - /// The resulting task sequence. - /// Thrown when the input sequence is null. - val concat: sources: taskSeq<#taskSeq<'T>> -> taskSeq<'T> - - /// - /// Concatenates task sequences and in order as a single - /// task sequence. - /// - /// - /// The first input task sequence. - /// The second input task sequence. - /// The resulting task sequence. - /// Thrown when either of the input sequences is null. - val append: source1: #taskSeq<'T> -> source2: #taskSeq<'T> -> taskSeq<'T> - - /// - /// Concatenates a task sequence with a non-async F# in - /// and returns a single task sequence. - /// - /// - /// The input task sequence. - /// The input F# sequence. - /// The resulting task sequence. - /// Thrown when either of the input sequences is null. - val appendSeq: source1: #taskSeq<'T> -> source2: #seq<'T> -> taskSeq<'T> - - /// - /// Concatenates a non-async F# in with a task sequence in - /// and returns a single task sequence. - /// - /// - /// The input F# sequence. - /// The input task sequence. - /// The resulting task sequence. - /// Thrown when either of the input sequences is null. - val prependSeq: source1: #seq<'T> -> source2: #taskSeq<'T> -> taskSeq<'T> - - /// Returns taskSeq as an array. This function is blocking until the sequence is exhausted and will properly dispose of the resources. - val toList: source: taskSeq<'T> -> 'T list - - /// Returns taskSeq as an array. This function is blocking until the sequence is exhausted and will properly dispose of the resources. - val toArray: source: taskSeq<'T> -> 'T[] - - /// - /// Returns the task sequence as an F# , that is, an - /// . This function is blocking at each , but otherwise - /// acts as a normal delay-executed sequence. - /// It will then dispose of the resources. - /// - /// - /// The input task sequence. - /// The resulting task sequence. - val toSeq: source: taskSeq<'T> -> seq<'T> - - /// Unwraps the taskSeq as a Task>. This function is non-blocking. - val toArrayAsync: source: taskSeq<'T> -> Task<'T[]> - - /// Unwraps the taskSeq as a Task>. This function is non-blocking. - val toListAsync: source: taskSeq<'T> -> Task<'T list> - - /// Unwraps the taskSeq as a Task>. This function is non-blocking. - val toResizeArrayAsync: source: taskSeq<'T> -> Task> - - /// Unwraps the taskSeq as a Task>. This function is non-blocking. - val toIListAsync: source: taskSeq<'T> -> Task> - - /// Create a taskSeq of an array. - val ofArray: source: 'T[] -> taskSeq<'T> - - /// Create a taskSeq of a list. - val ofList: source: 'T list -> taskSeq<'T> - - /// Create a taskSeq of a seq. - val ofSeq: source: seq<'T> -> taskSeq<'T> - - /// Create a taskSeq of a ResizeArray, aka List. - val ofResizeArray: source: ResizeArray<'T> -> taskSeq<'T> - - /// Create a taskSeq of a sequence of tasks, that may already have hot-started. - val ofTaskSeq: source: seq<#Task<'T>> -> taskSeq<'T> - - /// Create a taskSeq of a list of tasks, that may already have hot-started. - val ofTaskList: source: #Task<'T> list -> taskSeq<'T> - - /// Create a taskSeq of an array of tasks, that may already have hot-started. - val ofTaskArray: source: #Task<'T> array -> taskSeq<'T> - - /// Create a taskSeq of a seq of async. - val ofAsyncSeq: source: seq> -> taskSeq<'T> - - /// Create a taskSeq of a list of async. - val ofAsyncList: source: Async<'T> list -> taskSeq<'T> - - /// Create a taskSeq of an array of async. - val ofAsyncArray: source: Async<'T> array -> taskSeq<'T> - - /// - /// Boxes as type each item in the sequence asynchyronously. - /// - val box: source: taskSeq<'T> -> taskSeq - - /// - /// Unboxes to the target type each item in the sequence asynchyronously. - /// The target type must be a or a built-in value type. - /// - /// Thrown when the function is unable to cast an item to the target type. - val unbox<'U when 'U: struct> : source: taskSeq -> taskSeq<'U> - - /// - /// Casts each item in the untyped sequence asynchyronously. If your types are boxed struct types - /// it is recommended to use instead. - /// - /// Thrown when the function is unable to cast an item to the target type. - val cast: source: taskSeq -> taskSeq<'T> - - /// Iterates over the taskSeq applying the action function to each item. This function is non-blocking - /// exhausts the sequence as soon as the task is evaluated. - val iter: action: ('T -> unit) -> source: taskSeq<'T> -> Task - - /// Iterates over the taskSeq applying the action function to each item. This function is non-blocking, - /// exhausts the sequence as soon as the task is evaluated. - val iteri: action: (int -> 'T -> unit) -> source: taskSeq<'T> -> Task - - /// Iterates over the taskSeq applying the async action to each item. This function is non-blocking - /// exhausts the sequence as soon as the task is evaluated. - val iterAsync: action: ('T -> #Task) -> source: taskSeq<'T> -> Task - - /// Iterates over the taskSeq, applying the async action to each item. This function is non-blocking, - /// exhausts the sequence as soon as the task is evaluated. - val iteriAsync: action: (int -> 'T -> #Task) -> source: taskSeq<'T> -> Task - - /// Maps over the taskSeq, applying the mapper function to each item. This function is non-blocking. - val map: mapper: ('T -> 'U) -> source: taskSeq<'T> -> taskSeq<'U> - - /// - /// Builds a new task sequence whose elements are the corresponding elements of the input task - /// sequence paired with the integer index (from 0) of each element. - /// Does not evaluate the input sequence until requested. - /// - /// The input task sequence. - /// The resulting task sequence of tuples. - /// Thrown when the input sequence is null. - val indexed: source: taskSeq<'T> -> taskSeq - - /// Maps over the taskSeq with an index, applying the mapper function to each item. This function is non-blocking. - val mapi: mapper: (int -> 'T -> 'U) -> source: taskSeq<'T> -> taskSeq<'U> - - /// Maps over the taskSeq, applying the async mapper function to each item. This function is non-blocking. - val mapAsync: mapper: ('T -> #Task<'U>) -> source: taskSeq<'T> -> taskSeq<'U> - - /// Maps over the taskSeq with an index, applying the async mapper function to each item. This function is non-blocking. - val mapiAsync: mapper: (int -> 'T -> #Task<'U>) -> source: taskSeq<'T> -> taskSeq<'U> - - /// Applies the given function to the items in the taskSeq and concatenates all the results in order. - val collect: binder: ('T -> #taskSeq<'U>) -> source: taskSeq<'T> -> taskSeq<'U> - - /// Applies the given function to the items in the taskSeq and concatenates all the results in order. - val collectSeq: binder: ('T -> #seq<'U>) -> source: taskSeq<'T> -> taskSeq<'U> - - /// Applies the given async function to the items in the taskSeq and concatenates all the results in order. - val collectAsync: binder: ('T -> #Task<'TSeqU>) -> source: taskSeq<'T> -> taskSeq<'U> when 'TSeqU :> taskSeq<'U> - - /// Applies the given async function to the items in the taskSeq and concatenates all the results in order. - val collectSeqAsync: binder: ('T -> #Task<'SeqU>) -> source: taskSeq<'T> -> taskSeq<'U> when 'SeqU :> seq<'U> - - /// - /// Returns the first element of the task sequence from , or if the sequence is empty. - /// - val tryHead: source: taskSeq<'T> -> Task<'T option> - - /// - /// Returns the first elementof the task sequence from - /// - /// Thrown when the sequence is empty. - val head: source: taskSeq<'T> -> Task<'T> - - /// - /// Returns the whole task sequence from , minus its first element, or if the sequence is empty. - /// - val tryTail: source: taskSeq<'T> -> Task option> - - /// - /// Returns the whole task sequence from , minus its first element. - /// - /// Thrown when the sequence is empty. - val tail: source: taskSeq<'T> -> Task> - - /// - /// Returns the last element of the task sequence from , or if the sequence is empty. - /// - val tryLast: source: taskSeq<'T> -> Task<'T option> - - /// - /// Returns the last element of the . - /// - /// Thrown when the sequence is empty. - val last: source: taskSeq<'T> -> Task<'T> - - /// - /// Returns the nth element of the , or if the sequence - /// does not contain enough elements, or if is negative. - /// Parameter is zero-based, that is, the value 0 returns the first element. - /// - val tryItem: index: int -> source: taskSeq<'T> -> Task<'T option> - - /// - /// Returns the nth element of the , or if the sequence - /// does not contain enough elements, or if is negative. - /// - /// Thrown when the sequence has insufficient length or - /// is negative. - val item: index: int -> source: taskSeq<'T> -> Task<'T> - - /// - /// Returns the only element of the task sequence, or if the sequence is empty of - /// contains more than one element. - /// - val tryExactlyOne: source: taskSeq<'T> -> Task<'T option> - - /// - /// Returns the only element of the task sequence. - /// - /// Thrown when the input sequence does not contain precisely one element. - val exactlyOne: source: taskSeq<'T> -> Task<'T> - - /// - /// Applies the given function to each element of the task sequence. Returns - /// a sequence comprised of the results "x" for each element where - /// the function returns Some(x). - /// If is asynchronous, consider using . - /// - val choose: chooser: ('T -> 'U option) -> source: taskSeq<'T> -> taskSeq<'U> - - /// - /// Applies the given asynchronous function to each element of the task sequence. Returns - /// a sequence comprised of the results "x" for each element where - /// the function returns . - /// If does not need to be asynchronous, consider using . - /// - val chooseAsync: chooser: ('T -> #Task<'U option>) -> source: taskSeq<'T> -> taskSeq<'U> - - /// - /// Returns a new collection containing only the elements of the collection - /// for which the given function returns . - /// If is asynchronous, consider using . - /// - val filter: predicate: ('T -> bool) -> source: taskSeq<'T> -> taskSeq<'T> - - /// - /// Returns a new collection containing only the elements of the collection - /// for which the given asynchronous function returns . - /// If does not need to be asynchronous, consider using . - /// - val filterAsync: predicate: ('T -> #Task) -> source: taskSeq<'T> -> taskSeq<'T> - - /// - /// Applies the given function to successive elements of the task sequence - /// in , returning the first result where the function returns . - /// If is asynchronous, consider using . - /// - val tryPick: chooser: ('T -> 'U option) -> source: taskSeq<'T> -> Task<'U option> - - /// - /// Applies the given asynchronous function to successive elements of the task sequence - /// in , returning the first result where the function returns . - /// If does not need to be asynchronous, consider using . - /// - val tryPickAsync: chooser: ('T -> #Task<'U option>) -> source: taskSeq<'T> -> Task<'U option> - - /// - /// Returns the first element of the task sequence in for which the given function - /// returns . Returns if no such element exists. - /// If is asynchronous, consider using . - /// - val tryFind: predicate: ('T -> bool) -> source: taskSeq<'T> -> Task<'T option> - - /// - /// Returns the first element of the task sequence in for which the given asynchronous function - /// returns . Returns if no such element exists. - /// If does not need to be asynchronous, consider using . - /// - val tryFindAsync: predicate: ('T -> #Task) -> source: taskSeq<'T> -> Task<'T option> - - /// - /// Returns the index, starting from zero, of the task sequence in for which the given function - /// returns . Returns if no such element exists. - /// If is asynchronous, consider using . - /// - val tryFindIndex: predicate: ('T -> bool) -> source: taskSeq<'T> -> Task - - /// - /// Returns the index, starting from zero, of the task sequence in for which the given asynchronous function - /// returns . Returns if no such element exists. - /// If does not need to be asynchronous, consider using . - /// - val tryFindIndexAsync: predicate: ('T -> #Task) -> source: taskSeq<'T> -> Task - - - /// - /// Applies the given function to successive elements of the task sequence - /// in , returning the first result where the function returns . - /// If is asynchronous, consider using . - /// Thrown when every item of the sequence - /// evaluates to when the given function is applied. - /// - val pick: chooser: ('T -> 'U option) -> source: taskSeq<'T> -> Task<'U> - - /// - /// Applies the given asynchronous function to successive elements of the task sequence - /// in , returning the first result where the function returns . - /// If does not need to be asynchronous, consider using . - /// Thrown when every item of the sequence - /// evaluates to when the given function is applied. - /// - val pickAsync: chooser: ('T -> #Task<'U option>) -> source: taskSeq<'T> -> Task<'U> - - /// - /// Returns the first element of the task sequence in for which the given function - /// returns . - /// If is asynchronous, consider using . - /// - /// Thrown if no element returns when - /// evaluated by the function. - val find: predicate: ('T -> bool) -> source: taskSeq<'T> -> Task<'T> - - /// - /// Returns the first element of the task sequence in for which the given - /// asynchronous function returns . - /// If does not need to be asynchronous, consider using . - /// - /// Thrown if no element returns when - /// evaluated by the function. - val findAsync: predicate: ('T -> #Task) -> source: taskSeq<'T> -> Task<'T> - - /// - /// Returns the index, starting from zero, of the first element of the task sequence in for which - /// the given function returns . - /// If is asynchronous, consider using . - /// - /// Thrown if no element returns when - /// evaluated by the function. - val findIndex: predicate: ('T -> bool) -> source: taskSeq<'T> -> Task - - /// - /// Returns the index, starting from zero, of the task sequence in for which the given - /// asynchronous function returns . - /// If does not need to be asynchronous, consider using . - /// - /// - /// Thrown if no element returns when - /// evaluated by the function. - val findIndexAsync: predicate: ('T -> #Task) -> source: taskSeq<'T> -> Task - - /// - /// Tests if the sequence contains the specified element. Returns - /// if contains the specified element; - /// otherwise. - /// - /// - /// The value to locate in the input sequence. - /// The input sequence. - /// True if the input sequence contains the specified element; false otherwise. - /// Thrown when the input sequence is null. - val contains<'T when 'T: equality> : value: 'T -> source: taskSeq<'T> -> Task - - /// - /// Tests if any element of the task sequence in satisfies - /// the given . - /// The function is applied to the elements of the input sequence. If any application - /// returns then the overall result is and no further elements are evaluated and tested. - /// Otherwise, is returned. - /// - /// - /// A function to test each item of the input sequence. - /// The input sequence. /// - /// True if any result from the predicate is true; false otherwise. /// - /// Thrown when the input sequence is null. - val exists: predicate: ('T -> bool) -> source: taskSeq<'T> -> Task - - /// - /// Tests if any element of the task sequence in satisfies - /// the given async . - /// The function is applied to the elements of the input sequence. If any application - /// returns then the overall result is and no further elements are evaluated and tested. - /// Otherwise, is returned. - /// - /// - /// A function to test each item of the input sequence. - /// The input sequence. /// - /// True if any result from the predicate is true; false otherwise. /// - /// Thrown when the input sequence is null. - val existsAsync: predicate: ('T -> #Task) -> source: taskSeq<'T> -> Task - - /// - /// Returns a new task sequence with the distinct elements of the second task sequence which do not appear in the - /// , using generic hash and equality comparisons to compare values. - /// - /// - /// - /// Note that this function returns a task sequence that digests the whole of the first input task sequence as soon as - /// the result sequence first gets awaited or iterated. As a result this function should not be used with - /// large or infinite sequences in the first parameter. The function makes no assumption on the ordering of the first input - /// sequence. - /// - /// - /// A task sequence whose elements that also occur in the second sequence will cause those elements to be removed from the returned sequence. - /// A sequence whose elements that are not also in first will be returned. - /// A sequence that contains the set difference of the elements of two sequences. - /// - /// Thrown when either of the two input sequences is null. - val except<'T when 'T: equality> : itemsToExclude: taskSeq<'T> -> source: taskSeq<'T> -> taskSeq<'T> - - /// - /// Returns a new task sequence with the distinct elements of the second task sequence which do not appear in the - /// , using generic hash and equality comparisons to compare values. - /// - /// - /// - /// Note that this function returns a task sequence that digests the whole of the first input task sequence as soon as - /// the result sequence first gets awaited or iterated. As a result this function should not be used with - /// large or infinite sequences in the first parameter. The function makes no assumption on the ordering of the first input - /// sequence. - /// - /// - /// A task sequence whose elements that also occur in the second sequence will cause those elements to be removed from the returned sequence. - /// A sequence whose elements that are not also in first will be returned. - /// A sequence that contains the set difference of the elements of two sequences. - /// - /// Thrown when either of the two input sequences is null. - val exceptOfSeq<'T when 'T: equality> : itemsToExclude: seq<'T> -> source: taskSeq<'T> -> taskSeq<'T> - - /// - /// Zips two task sequences, returning a taskSeq of the tuples of each sequence, in order. May raise ArgumentException - /// if the sequences are or unequal length. - /// - /// The sequences have different lengths. - val zip: source1: taskSeq<'T> -> source2: taskSeq<'U> -> taskSeq<'T * 'U> - - /// - /// Applies the function to each element in the task sequence, - /// threading an accumulator argument of type through the computation. - /// If the accumulator function is asynchronous, consider using . - /// - val fold: folder: ('State -> 'T -> 'State) -> state: 'State -> source: taskSeq<'T> -> Task<'State> - - /// - /// Applies the asynchronous function to each element in the task sequence, - /// threading an accumulator argument of type through the computation. - /// If the accumulator function does not need to be asynchronous, consider using . - /// - val foldAsync: folder: ('State -> 'T -> #Task<'State>) -> state: 'State -> source: taskSeq<'T> -> Task<'State> diff --git a/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs b/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs index ea154804..a9d4d52f 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs +++ b/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs @@ -40,7 +40,7 @@ module Internal = // cannot be marked with 'internal' scope /// Call MoveNext on an IAsyncStateMachine by reference let inline moveNextRef (x: byref<'T> when 'T :> IAsyncStateMachine) = x.MoveNext() - // F# requires that we implement interfaces even on an abstract class + /// F# requires that we implement interfaces even on an abstract class let inline raiseNotImpl () = NotImplementedException "Abstract Class: method or property not implemented" |> raise @@ -660,3 +660,8 @@ module HighPriority = sm.Data.awaiter <- awaiter sm.Data.current <- ValueNone false) + +[] +module TaskSeqBuilder = + /// Builds an asynchronous task sequence based on IAsyncEnumerable<'T> using computation expression syntax. + let taskSeq = TaskSeqBuilder() diff --git a/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fsi b/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fsi new file mode 100644 index 00000000..bfd082f8 --- /dev/null +++ b/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fsi @@ -0,0 +1,166 @@ +namespace FSharp.Control + +open System +open System.Threading +open System.Threading.Tasks +open System.Threading.Tasks.Sources +open System.Runtime.CompilerServices +open System.Collections.Generic + +open FSharp.Core.CompilerServices + +[] +module Internal = + + /// Setting from environment variable TASKSEQ_LOG_VERBOSE, which, + /// when set, enables (very) verbose printing of flow and state + val initVerbose: unit -> bool + + /// Call MoveNext on an IAsyncStateMachine by reference + val inline moveNextRef: x: byref<#IAsyncStateMachine> -> unit + + val inline raiseNotImpl: unit -> 'a + +type taskSeq<'T> = IAsyncEnumerable<'T> + +[] +type TaskSeqStateMachineData<'T> = + + new: unit -> TaskSeqStateMachineData<'T> + + [] + val mutable cancellationToken: CancellationToken + + /// Keeps track of the objects that need to be disposed off on IAsyncDispose. + [] + val mutable disposalStack: ResizeArray<(unit -> Task)> + + [] + val mutable awaiter: ICriticalNotifyCompletion + + [] + val mutable promiseOfValueOrEnd: ManualResetValueTaskSourceCore + + /// Helper struct providing methods for awaiting 'next' in async iteration scenarios. + [] + val mutable builder: AsyncIteratorMethodBuilder + + /// Whether or not a full iteration through the IAsyncEnumerator has completed + [] + val mutable completed: bool + + /// Used by the AsyncEnumerator interface to return the Current value when + /// IAsyncEnumerator.Current is called + [] + val mutable current: ValueOption<'T> + + /// A reference to 'self', because otherwise we can't use byref in the resumable code. + [] + val mutable boxedSelf: TaskSeq<'T> + + member PopDispose: unit -> unit + + member PushDispose: disposer: (unit -> Task) -> unit + +and [] TaskSeq<'T> = + interface IValueTaskSource + interface IValueTaskSource + interface IAsyncStateMachine + interface IAsyncEnumerable<'T> + interface IAsyncEnumerator<'T> + + new: unit -> TaskSeq<'T> + + abstract MoveNextAsyncResult: unit -> ValueTask + +and [] TaskSeq<'Machine, 'T + when 'Machine :> IAsyncStateMachine and 'Machine :> IResumableStateMachine>> = + inherit TaskSeq<'T> + interface IAsyncEnumerator<'T> + interface IAsyncEnumerable<'T> + interface IAsyncStateMachine + interface IValueTaskSource + interface IValueTaskSource + + new: unit -> TaskSeq<'Machine, 'T> + + [] + val mutable _initialMachine: 'Machine + + /// Keeps the active state machine. + [] + val mutable _machine: 'Machine + + //new: unit -> TaskSeq<'Machine, 'T> + member InitMachineData: ct: CancellationToken * machine: byref<'Machine> -> unit + override MoveNextAsyncResult: unit -> ValueTask + +and TaskSeqCode<'T> = ResumableCode, unit> +and TaskSeqStateMachine<'T> = ResumableStateMachine> +and TaskSeqResumptionFunc<'T> = ResumptionFunc> +and TaskSeqResumptionDynamicInfo<'T> = ResumptionDynamicInfo> + +[] +type TaskSeqBuilder = + + member inline Combine: task1: TaskSeqCode<'T> * task2: TaskSeqCode<'T> -> TaskSeqCode<'T> + member inline Delay: f: (unit -> TaskSeqCode<'T>) -> TaskSeqCode<'T> + member inline Run: code: TaskSeqCode<'T> -> IAsyncEnumerable<'T> + member inline TryFinally: body: TaskSeqCode<'T> * compensation: (unit -> unit) -> TaskSeqCode<'T> + member inline TryFinallyAsync: body: TaskSeqCode<'T> * compensation: (unit -> Task) -> TaskSeqCode<'T> + member inline TryWith: body: TaskSeqCode<'T> * catch: (exn -> TaskSeqCode<'T>) -> TaskSeqCode<'T> + member inline Using: disp: 'a * body: ('a -> TaskSeqCode<'T>) -> TaskSeqCode<'T> when 'a :> IAsyncDisposable + member inline While: condition: (unit -> bool) * body: TaskSeqCode<'T> -> TaskSeqCode<'T> + /// Used by `For`. F# currently doesn't support `while!`, so this cannot be called directly from the CE + member inline WhileAsync: condition: (unit -> ValueTask) * body: TaskSeqCode<'T> -> TaskSeqCode<'T> + member inline Yield: v: 'T -> TaskSeqCode<'T> + member inline Zero: unit -> TaskSeqCode<'T> + +[] +module TaskSeqBuilder = + + /// + /// Builds an asynchronous task sequence based on using computation expression syntax. + /// + val taskSeq: TaskSeqBuilder + +[] +module LowPriority = + type TaskSeqBuilder with + + [] + member inline Bind< ^TaskLike, 'TResult1, 'TResult2, ^Awaiter, 'TOverall> : + task: ^TaskLike * continuation: ('TResult1 -> TaskSeqCode<'TResult2>) -> TaskSeqCode<'TResult2> + when ^TaskLike: (member GetAwaiter: unit -> ^Awaiter) + and ^Awaiter :> ICriticalNotifyCompletion + and ^Awaiter: (member get_IsCompleted: unit -> bool) + and ^Awaiter: (member GetResult: unit -> 'TResult1) + +[] +module MediumPriority = + type TaskSeqBuilder with + + member inline Using: disp: 'a * body: ('a -> TaskSeqCode<'T>) -> TaskSeqCode<'T> when 'a :> IDisposable + + type TaskSeqBuilder with + + member inline For: sequence: seq<'TElement> * body: ('TElement -> TaskSeqCode<'T>) -> TaskSeqCode<'T> + + type TaskSeqBuilder with + + member inline YieldFrom: source: seq<'T> -> TaskSeqCode<'T> + + type TaskSeqBuilder with + + member inline For: + source: #IAsyncEnumerable<'TElement> * body: ('TElement -> TaskSeqCode<'T>) -> TaskSeqCode<'T> + + type TaskSeqBuilder with + + member inline YieldFrom: source: IAsyncEnumerable<'T> -> TaskSeqCode<'T> + +[] +module HighPriority = + type TaskSeqBuilder with + + member inline Bind: task: Task<'TResult1> * continuation: ('TResult1 -> TaskSeqCode<'T>) -> TaskSeqCode<'T> diff --git a/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs b/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs index deeaefbd..8f7446ef 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs +++ b/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs @@ -5,41 +5,36 @@ open System.Collections.Generic open System.Threading open System.Threading.Tasks -[] -module ExtraTaskSeqOperators = - /// A TaskSeq workflow for IAsyncEnumerable<'T> types. - let taskSeq = TaskSeqBuilder() - [] -type AsyncEnumStatus = +type internal AsyncEnumStatus = | BeforeAll | WithCurrent | AfterAll [] -type Action<'T, 'U, 'TaskU when 'TaskU :> Task<'U>> = +type internal Action<'T, 'U, 'TaskU when 'TaskU :> Task<'U>> = | CountableAction of countable_action: (int -> 'T -> 'U) | SimpleAction of simple_action: ('T -> 'U) | AsyncCountableAction of async_countable_action: (int -> 'T -> 'TaskU) | AsyncSimpleAction of async_simple_action: ('T -> 'TaskU) [] -type FolderAction<'T, 'State, 'TaskState when 'TaskState :> Task<'State>> = +type internal FolderAction<'T, 'State, 'TaskState when 'TaskState :> Task<'State>> = | FolderAction of state_action: ('State -> 'T -> 'State) | AsyncFolderAction of async_state_action: ('State -> 'T -> 'TaskState) [] -type ChooserAction<'T, 'U, 'TaskOption when 'TaskOption :> Task<'U option>> = +type internal ChooserAction<'T, 'U, 'TaskOption when 'TaskOption :> Task<'U option>> = | TryPick of try_pick: ('T -> 'U option) | TryPickAsync of async_try_pick: ('T -> 'TaskOption) [] -type PredicateAction<'T, 'TaskBool when 'TaskBool :> Task> = +type internal PredicateAction<'T, 'TaskBool when 'TaskBool :> Task> = | Predicate of try_filter: ('T -> bool) | PredicateAsync of async_try_filter: ('T -> 'TaskBool) [] -type InitAction<'T, 'TaskT when 'TaskT :> Task<'T>> = +type internal InitAction<'T, 'TaskT when 'TaskT :> Task<'T>> = | InitAction of init_item: (int -> 'T) | InitActionAsync of async_init_item: (int -> 'TaskT) diff --git a/src/FSharp.Control.TaskSeq/Utils.fs b/src/FSharp.Control.TaskSeq/Utils.fs index 5b35fcaa..63274ed4 100644 --- a/src/FSharp.Control.TaskSeq/Utils.fs +++ b/src/FSharp.Control.TaskSeq/Utils.fs @@ -115,52 +115,3 @@ module Async = /// Bind an Async<'T> let inline bind binder (task: Async<'T>) : Async<'U> = ExtraTopLevelOperators.async { return! binder task } - -type Debug = - - [] - static val mutable private verbose: bool option - - /// Setting from environment variable TASKSEQ_LOG_VERBOSE, which, - /// when set, enables (very) verbose printing of flow and state - static member private getVerboseSetting() = - match Debug.verbose with - | None -> - let verboseEnv = - try - match Environment.GetEnvironmentVariable "TASKSEQ_LOG_VERBOSE" with - | null -> false - | x -> - match x.ToLowerInvariant().Trim() with - | "1" - | "true" - | "on" - | "yes" -> true - | _ -> false - - with _ -> - false - - Debug.verbose <- Some verboseEnv - verboseEnv - - | Some setting -> setting - - /// Private helper to log to stdout in DEBUG builds only - [] - static member private print value = - match Debug.getVerboseSetting () with - | false -> () - | true -> - // don't use ksprintf here, because the compiler does not remove all allocations due to - // the way PrintfFormat types are compiled, even if we set the Conditional attribute. - let ct = Thread.CurrentThread - printfn "%i (%b): %s" ct.ManagedThreadId ct.IsThreadPoolThread value - - /// Log to stdout in DEBUG builds only - [] - static member logInfo(str) = Debug.print str - - /// Log to stdout in DEBUG builds only - [] - static member logInfo(str, data) = Debug.print $"%s{str}{data}" diff --git a/src/FSharp.Control.TaskSeq/Utils.fsi b/src/FSharp.Control.TaskSeq/Utils.fsi new file mode 100644 index 00000000..219d8e0a --- /dev/null +++ b/src/FSharp.Control.TaskSeq/Utils.fsi @@ -0,0 +1,87 @@ +namespace FSharp.Control + +open System.Diagnostics +open System.Threading.Tasks +open System.Threading.Tasks.Sources + +[] +module ValueTaskExtensions = + type System.Threading.Tasks.ValueTask with + + /// (Extension member) Gets a task that has already completed successfully. + static member inline CompletedTask: System.Threading.Tasks.ValueTask + +module ValueTask = + + /// A successfully completed ValueTask of boolean that has the value false. + val False: ValueTask + + /// A successfully completed ValueTask of boolean that has the value true. + val True: ValueTask + + /// Creates a ValueTask with the supplied result of the successful operation. + val inline FromResult: x: 'T -> ValueTask<'T> + + /// Creates a ValueTask with an IValueTaskSource representing the operation + val inline ofIValueTaskSource: taskSource: IValueTaskSource -> version: int16 -> ValueTask + + /// Creates a ValueTask form a Task<'T> + val inline ofTask: task: Task<'T> -> ValueTask<'T> + + /// Ignore a ValueTask<'T>, returns a non-generic ValueTask. + val inline ignore: vtask: ValueTask<'T> -> ValueTask + +module Task = + + /// Convert an Async<'T> into a Task<'T> + val inline ofAsync: async: Async<'T> -> Task<'T> + + /// Convert a unit-task into a Task + val inline ofTask: task': Task -> Task + + /// Convert a non-task function into a task-returning function + val inline apply: func: ('a -> 'b) -> ('a -> Task<'b>) + + /// Convert a Task<'T> into an Async<'T> + val inline toAsync: task: Task<'T> -> Async<'T> + + /// Convert a Task<'T> into a ValueTask<'T> + val inline toValueTask: task: Task<'T> -> ValueTask<'T> + + /// + /// Convert a ValueTask<'T> to a Task<'T>. To use a non-generic ValueTask, + /// consider using: . + /// + val inline ofValueTask: valueTask: ValueTask<'T> -> Task<'T> + + /// Convert a Task<'T> into a non-generic Task, ignoring the result + val inline ignore: task: Task<'T> -> Task + + /// Map a Task<'T> + val inline map: mapper: ('T -> 'U) -> task: Task<'T> -> Task<'U> + + /// Bind a Task<'T> + val inline bind: binder: ('T -> #Task<'U>) -> task: Task<'T> -> Task<'U> + + /// Create a task from a value + val inline fromResult: value: 'U -> Task<'U> + +module Async = + + /// Convert an Task<'T> into an Async<'T> + val inline ofTask: task: Task<'T> -> Async<'T> + + /// Convert a unit-task into an Async + val inline ofUnitTask: task: Task -> Async + + /// Convert a Task<'T> into an Async<'T> + val inline toTask: async: Async<'T> -> Task<'T> + + /// Convert an Async<'T> into an Async, ignoring the result + val inline ignore: async': Async<'T> -> Async + + /// Map an Async<'T> + val inline map: mapper: ('T -> 'U) -> async: Async<'T> -> Async<'U> + + /// Bind an Async<'T> + val inline bind: binder: (Async<'T> -> Async<'U>) -> task: Async<'T> -> Async<'U> From 966d28c3312ddc6751370db40e04129f462f11ea Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Mon, 28 Nov 2022 00:01:04 +0100 Subject: [PATCH 64/68] Remove `TaskSeqResumptionFunc` and `TaskSeqResumptionDynamicInfo` from the public surface --- src/FSharp.Control.TaskSeq/TaskSeqBuilder.fsi | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fsi b/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fsi index bfd082f8..7040e2e9 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fsi +++ b/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fsi @@ -97,8 +97,6 @@ and [] TaskSeq<'Machine, 'T and TaskSeqCode<'T> = ResumableCode, unit> and TaskSeqStateMachine<'T> = ResumableStateMachine> -and TaskSeqResumptionFunc<'T> = ResumptionFunc> -and TaskSeqResumptionDynamicInfo<'T> = ResumptionDynamicInfo> [] type TaskSeqBuilder = From 64848166facb655b1bfacdb7ff7b03044a5d23e6 Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Mon, 28 Nov 2022 03:22:28 +0100 Subject: [PATCH 65/68] Update descriptions and add warnings for use of internal-only public types --- src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs | 66 +++++----- src/FSharp.Control.TaskSeq/TaskSeqBuilder.fsi | 113 +++++++++++------- 2 files changed, 102 insertions(+), 77 deletions(-) diff --git a/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs b/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs index a9d4d52f..bdf5c725 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs +++ b/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs @@ -19,8 +19,6 @@ open FSharp.Control [] module Internal = // cannot be marked with 'internal' scope - /// Setting from environment variable TASKSEQ_LOG_VERBOSE, which, - /// when set, enables (very) verbose printing of flow and state let initVerbose () = try match Environment.GetEnvironmentVariable "TASKSEQ_LOG_VERBOSE" with @@ -37,10 +35,8 @@ module Internal = // cannot be marked with 'internal' scope false - /// Call MoveNext on an IAsyncStateMachine by reference let inline moveNextRef (x: byref<'T> when 'T :> IAsyncStateMachine) = x.MoveNext() - /// F# requires that we implement interfaces even on an abstract class let inline raiseNotImpl () = NotImplementedException "Abstract Class: method or property not implemented" |> raise @@ -79,7 +75,7 @@ type TaskSeqStateMachineData<'T>() = /// A reference to 'self', because otherwise we can't use byref in the resumable code. [] - val mutable boxedSelf: TaskSeq<'T> + val mutable boxedSelf: TaskSeqBase<'T> member data.PushDispose(disposer: unit -> Task) = if isNull data.disposalStack then @@ -91,7 +87,7 @@ type TaskSeqStateMachineData<'T>() = if not (isNull data.disposalStack) then data.disposalStack.RemoveAt(data.disposalStack.Count - 1) -and [] TaskSeq<'T>() = +and [] TaskSeqBase<'T>() = abstract MoveNextAsyncResult: unit -> ValueTask @@ -119,7 +115,7 @@ and [] TaskSeq<'T>() = and [] TaskSeq<'Machine, 'T when 'Machine :> IAsyncStateMachine and 'Machine :> IResumableStateMachine>>() = - inherit TaskSeq<'T>() + inherit TaskSeqBase<'T>() let initialThreadId = Environment.CurrentManagedThreadId /// Shadows the initial machine, just after it is initialized by the F# compiler-generated state. @@ -304,16 +300,16 @@ and [] TaskSeq<'Machine, 'T // assume it's a possibly new, not yet supported case, treat as default ValueTask.ofIValueTaskSource this version -and TaskSeqCode<'T> = ResumableCode, unit> +and ResumableTSC<'T> = ResumableCode, unit> and TaskSeqStateMachine<'T> = ResumableStateMachine> and TaskSeqResumptionFunc<'T> = ResumptionFunc> and TaskSeqResumptionDynamicInfo<'T> = ResumptionDynamicInfo> type TaskSeqBuilder() = - member inline _.Delay(f: unit -> TaskSeqCode<'T>) : TaskSeqCode<'T> = TaskSeqCode<'T>(fun sm -> f().Invoke(&sm)) + member inline _.Delay(f: unit -> ResumableTSC<'T>) : ResumableTSC<'T> = ResumableTSC<'T>(fun sm -> f().Invoke(&sm)) - member inline _.Run(code: TaskSeqCode<'T>) : IAsyncEnumerable<'T> = + member inline _.Run(code: ResumableTSC<'T>) : IAsyncEnumerable<'T> = if __useResumableCode then // This is the static implementation. A new struct type is created. __stateMachine, IAsyncEnumerable<'T>> @@ -386,11 +382,11 @@ type TaskSeqBuilder() = |> raise - member inline _.Zero() : TaskSeqCode<'T> = + member inline _.Zero() : ResumableTSC<'T> = Debug.logInfo "at Zero()" ResumableCode.Zero() - member inline _.Combine(task1: TaskSeqCode<'T>, task2: TaskSeqCode<'T>) : TaskSeqCode<'T> = + member inline _.Combine(task1: ResumableTSC<'T>, task2: ResumableTSC<'T>) : ResumableTSC<'T> = Debug.logInfo "at Combine(.., ..)" ResumableCode.Combine(task1, task2) @@ -399,8 +395,8 @@ type TaskSeqBuilder() = member inline _.WhileAsync ( [] condition: unit -> ValueTask, - body: TaskSeqCode<'T> - ) : TaskSeqCode<'T> = + body: ResumableTSC<'T> + ) : ResumableTSC<'T> = let mutable condition_res = true ResumableCode.While( @@ -436,17 +432,17 @@ type TaskSeqBuilder() = false) ) - member inline b.While([] condition: unit -> bool, body: TaskSeqCode<'T>) : TaskSeqCode<'T> = + member inline b.While([] condition: unit -> bool, body: ResumableTSC<'T>) : ResumableTSC<'T> = Debug.logInfo "at While(...)" ResumableCode.While(condition, body) - member inline _.TryWith(body: TaskSeqCode<'T>, catch: exn -> TaskSeqCode<'T>) : TaskSeqCode<'T> = + member inline _.TryWith(body: ResumableTSC<'T>, catch: exn -> ResumableTSC<'T>) : ResumableTSC<'T> = ResumableCode.TryWith(body, catch) - member inline _.TryFinallyAsync(body: TaskSeqCode<'T>, compensation: unit -> Task) : TaskSeqCode<'T> = + member inline _.TryFinallyAsync(body: ResumableTSC<'T>, compensation: unit -> Task) : ResumableTSC<'T> = ResumableCode.TryFinallyAsync( - TaskSeqCode<'T>(fun sm -> + ResumableTSC<'T>(fun sm -> sm.Data.PushDispose(fun () -> compensation ()) body.Invoke(&sm)), @@ -467,9 +463,9 @@ type TaskSeqBuilder() = __stack_condition_fin) ) - member inline _.TryFinally(body: TaskSeqCode<'T>, compensation: unit -> unit) : TaskSeqCode<'T> = + member inline _.TryFinally(body: ResumableTSC<'T>, compensation: unit -> unit) : ResumableTSC<'T> = ResumableCode.TryFinally( - TaskSeqCode<'T>(fun sm -> + ResumableTSC<'T>(fun sm -> sm.Data.PushDispose(fun () -> compensation () Task.CompletedTask) @@ -482,7 +478,7 @@ type TaskSeqBuilder() = true) ) - member inline this.Using(disp: #IAsyncDisposable, body: #IAsyncDisposable -> TaskSeqCode<'T>) : TaskSeqCode<'T> = + member inline this.Using(disp: #IAsyncDisposable, body: #IAsyncDisposable -> ResumableTSC<'T>) : ResumableTSC<'T> = // A using statement is just a try/finally with the finally block disposing if non-null. this.TryFinallyAsync( @@ -494,8 +490,8 @@ type TaskSeqBuilder() = Task.CompletedTask) ) - member inline _.Yield(v: 'T) : TaskSeqCode<'T> = - TaskSeqCode<'T>(fun sm -> + member inline _.Yield(v: 'T) : ResumableTSC<'T> = + ResumableTSC<'T>(fun sm -> // This will yield with __stack_fin = false // This will resume with __stack_fin = true Debug.logInfo "at Yield" @@ -546,10 +542,10 @@ module LowPriority = and ^Awaiter: (member GetResult: unit -> 'TResult1)> ( task: ^TaskLike, - continuation: ('TResult1 -> TaskSeqCode<'TResult2>) - ) : TaskSeqCode<'TResult2> = + continuation: ('TResult1 -> ResumableTSC<'TResult2>) + ) : ResumableTSC<'TResult2> = - TaskSeqCode<'TResult2>(fun sm -> + ResumableTSC<'TResult2>(fun sm -> let mutable awaiter = (^TaskLike: (member GetAwaiter: unit -> ^Awaiter) (task)) let mutable __stack_fin = true @@ -581,7 +577,7 @@ module LowPriority = module MediumPriority = type TaskSeqBuilder with - member inline this.Using(disp: #IDisposable, body: #IDisposable -> TaskSeqCode<'T>) : TaskSeqCode<'T> = + member inline this.Using(disp: #IDisposable, body: #IDisposable -> ResumableTSC<'T>) : ResumableTSC<'T> = // A using statement is just a try/finally with the finally block disposing if non-null. this.TryFinally( @@ -592,7 +588,7 @@ module MediumPriority = disp.Dispose()) ) - member inline this.For(sequence: seq<'TElement>, body: 'TElement -> TaskSeqCode<'T>) : TaskSeqCode<'T> = + member inline this.For(sequence: seq<'TElement>, body: 'TElement -> ResumableTSC<'T>) : ResumableTSC<'T> = // A for loop is just a using statement on the sequence's enumerator... this.Using( sequence.GetEnumerator(), @@ -600,14 +596,14 @@ module MediumPriority = (fun e -> this.While((fun () -> e.MoveNext()), (fun sm -> (body e.Current).Invoke(&sm)))) ) - member inline this.YieldFrom(source: seq<'T>) : TaskSeqCode<'T> = this.For(source, (fun v -> this.Yield(v))) + member inline this.YieldFrom(source: seq<'T>) : ResumableTSC<'T> = this.For(source, (fun v -> this.Yield(v))) member inline this.For ( source: #IAsyncEnumerable<'TElement>, - body: 'TElement -> TaskSeqCode<'T> - ) : TaskSeqCode<'T> = - TaskSeqCode<'T>(fun sm -> + body: 'TElement -> ResumableTSC<'T> + ) : ResumableTSC<'T> = + ResumableTSC<'T>(fun sm -> this .Using( source.GetAsyncEnumerator(sm.Data.cancellationToken), @@ -616,7 +612,7 @@ module MediumPriority = ) .Invoke(&sm)) - member inline this.YieldFrom(source: IAsyncEnumerable<'T>) : TaskSeqCode<'T> = + member inline this.YieldFrom(source: IAsyncEnumerable<'T>) : ResumableTSC<'T> = this.For(source, (fun v -> this.Yield(v))) [] @@ -633,8 +629,8 @@ module HighPriority = // - In contrast, ValueTask<_> *does have* GetResult() -> 'TResult // - Conclusion: we do not need an extra overload anymore for ValueTask // - member inline _.Bind(task: Task<'TResult1>, continuation: ('TResult1 -> TaskSeqCode<'T>)) : TaskSeqCode<'T> = - TaskSeqCode<'T>(fun sm -> + member inline _.Bind(task: Task<'TResult1>, continuation: ('TResult1 -> ResumableTSC<'T>)) : ResumableTSC<'T> = + ResumableTSC<'T>(fun sm -> let mutable awaiter = task.GetAwaiter() let mutable __stack_fin = true diff --git a/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fsi b/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fsi index 7040e2e9..4f115910 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fsi +++ b/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fsi @@ -12,19 +12,37 @@ open FSharp.Core.CompilerServices [] module Internal = - /// Setting from environment variable TASKSEQ_LOG_VERBOSE, which, + /// + /// Setting from environment variable , which, /// when set, enables (very) verbose printing of flow and state + /// val initVerbose: unit -> bool /// Call MoveNext on an IAsyncStateMachine by reference val inline moveNextRef: x: byref<#IAsyncStateMachine> -> unit + /// F# requires that we implement interfaces even on an abstract class. val inline raiseNotImpl: unit -> 'a +/// +/// Result of any computation expression, alias for . +/// type taskSeq<'T> = IAsyncEnumerable<'T> -[] -type TaskSeqStateMachineData<'T> = +/// TaskSeqCode type alias of ResumableCode delegate type, specially recognized by the F# compiler +and ResumableTSC<'T> = ResumableCode, unit> + +/// +/// Contains the state data for the computation expression builder. +/// For use in this library only. Required by the method. +/// +and TaskSeqStateMachine<'T> = ResumableStateMachine> + +/// +/// Contains the state data for the computation expression builder. +/// For use in this library only. Required by the method. +/// +and [] TaskSeqStateMachineData<'T> = new: unit -> TaskSeqStateMachineData<'T> @@ -56,26 +74,35 @@ type TaskSeqStateMachineData<'T> = /// A reference to 'self', because otherwise we can't use byref in the resumable code. [] - val mutable boxedSelf: TaskSeq<'T> + val mutable boxedSelf: TaskSeqBase<'T> member PopDispose: unit -> unit member PushDispose: disposer: (unit -> Task) -> unit -and [] TaskSeq<'T> = +/// +/// Abstract base class for . +/// For use by this library only, should not be used directly in user code. Its operation depends highly on resumable state. +/// +and [] TaskSeqBase<'T> = interface IValueTaskSource interface IValueTaskSource interface IAsyncStateMachine interface IAsyncEnumerable<'T> interface IAsyncEnumerator<'T> - new: unit -> TaskSeq<'T> + new: unit -> TaskSeqBase<'T> abstract MoveNextAsyncResult: unit -> ValueTask +/// +/// Main implementation of generic and related interfaces, +/// which forms the meat of the logic behind computation expresssions. +/// For use by this library only, should not be used directly in user code. Its operation depends highly on resumable state. +/// and [] TaskSeq<'Machine, 'T when 'Machine :> IAsyncStateMachine and 'Machine :> IResumableStateMachine>> = - inherit TaskSeq<'T> + inherit TaskSeqBase<'T> interface IAsyncEnumerator<'T> interface IAsyncEnumerable<'T> interface IAsyncStateMachine @@ -95,24 +122,24 @@ and [] TaskSeq<'Machine, 'T member InitMachineData: ct: CancellationToken * machine: byref<'Machine> -> unit override MoveNextAsyncResult: unit -> ValueTask -and TaskSeqCode<'T> = ResumableCode, unit> -and TaskSeqStateMachine<'T> = ResumableStateMachine> - +/// +/// Main builder class for the computation expression. +/// [] type TaskSeqBuilder = - member inline Combine: task1: TaskSeqCode<'T> * task2: TaskSeqCode<'T> -> TaskSeqCode<'T> - member inline Delay: f: (unit -> TaskSeqCode<'T>) -> TaskSeqCode<'T> - member inline Run: code: TaskSeqCode<'T> -> IAsyncEnumerable<'T> - member inline TryFinally: body: TaskSeqCode<'T> * compensation: (unit -> unit) -> TaskSeqCode<'T> - member inline TryFinallyAsync: body: TaskSeqCode<'T> * compensation: (unit -> Task) -> TaskSeqCode<'T> - member inline TryWith: body: TaskSeqCode<'T> * catch: (exn -> TaskSeqCode<'T>) -> TaskSeqCode<'T> - member inline Using: disp: 'a * body: ('a -> TaskSeqCode<'T>) -> TaskSeqCode<'T> when 'a :> IAsyncDisposable - member inline While: condition: (unit -> bool) * body: TaskSeqCode<'T> -> TaskSeqCode<'T> + member inline Combine: task1: ResumableTSC<'T> * task2: ResumableTSC<'T> -> ResumableTSC<'T> + member inline Delay: f: (unit -> ResumableTSC<'T>) -> ResumableTSC<'T> + member inline Run: code: ResumableTSC<'T> -> taskSeq<'T> + member inline TryFinally: body: ResumableTSC<'T> * compensation: (unit -> unit) -> ResumableTSC<'T> + member inline TryFinallyAsync: body: ResumableTSC<'T> * compensation: (unit -> Task) -> ResumableTSC<'T> + member inline TryWith: body: ResumableTSC<'T> * catch: (exn -> ResumableTSC<'T>) -> ResumableTSC<'T> + member inline Using: disp: 'a * body: ('a -> ResumableTSC<'T>) -> ResumableTSC<'T> when 'a :> IAsyncDisposable + member inline While: condition: (unit -> bool) * body: ResumableTSC<'T> -> ResumableTSC<'T> /// Used by `For`. F# currently doesn't support `while!`, so this cannot be called directly from the CE - member inline WhileAsync: condition: (unit -> ValueTask) * body: TaskSeqCode<'T> -> TaskSeqCode<'T> - member inline Yield: v: 'T -> TaskSeqCode<'T> - member inline Zero: unit -> TaskSeqCode<'T> + member inline WhileAsync: condition: (unit -> ValueTask) * body: ResumableTSC<'T> -> ResumableTSC<'T> + member inline Yield: v: 'T -> ResumableTSC<'T> + member inline Zero: unit -> ResumableTSC<'T> [] module TaskSeqBuilder = @@ -122,43 +149,45 @@ module TaskSeqBuilder = /// val taskSeq: TaskSeqBuilder +/// +/// Contains low priority extension methods for the main builder class for the computation expression. +/// The , and modules are not meant to be +/// accessed directly from user code. They solely serve to disambiguate overload resolution inside the computation expression. +/// [] module LowPriority = type TaskSeqBuilder with [] member inline Bind< ^TaskLike, 'TResult1, 'TResult2, ^Awaiter, 'TOverall> : - task: ^TaskLike * continuation: ('TResult1 -> TaskSeqCode<'TResult2>) -> TaskSeqCode<'TResult2> + task: ^TaskLike * continuation: ('TResult1 -> ResumableTSC<'TResult2>) -> ResumableTSC<'TResult2> when ^TaskLike: (member GetAwaiter: unit -> ^Awaiter) and ^Awaiter :> ICriticalNotifyCompletion and ^Awaiter: (member get_IsCompleted: unit -> bool) and ^Awaiter: (member GetResult: unit -> 'TResult1) +/// +/// Contains low priority extension methods for the main builder class for the computation expression. +/// The , and modules are not meant to be +/// accessed directly from user code. They solely serve to disambiguate overload resolution inside the computation expression. +/// [] module MediumPriority = type TaskSeqBuilder with - member inline Using: disp: 'a * body: ('a -> TaskSeqCode<'T>) -> TaskSeqCode<'T> when 'a :> IDisposable - - type TaskSeqBuilder with - - member inline For: sequence: seq<'TElement> * body: ('TElement -> TaskSeqCode<'T>) -> TaskSeqCode<'T> - - type TaskSeqBuilder with - - member inline YieldFrom: source: seq<'T> -> TaskSeqCode<'T> - - type TaskSeqBuilder with - - member inline For: - source: #IAsyncEnumerable<'TElement> * body: ('TElement -> TaskSeqCode<'T>) -> TaskSeqCode<'T> - - type TaskSeqBuilder with - - member inline YieldFrom: source: IAsyncEnumerable<'T> -> TaskSeqCode<'T> - + member inline Using: disp: 'a * body: ('a -> ResumableTSC<'T>) -> ResumableTSC<'T> when 'a :> IDisposable + member inline For: sequence: seq<'TElement> * body: ('TElement -> ResumableTSC<'T>) -> ResumableTSC<'T> + member inline YieldFrom: source: seq<'T> -> ResumableTSC<'T> + member inline For: source: #taskSeq<'TElement> * body: ('TElement -> ResumableTSC<'T>) -> ResumableTSC<'T> + member inline YieldFrom: source: taskSeq<'T> -> ResumableTSC<'T> + +/// +/// Contains low priority extension methods for the main builder class for the computation expression. +/// The , and modules are not meant to be +/// accessed directly from user code. They solely serve to disambiguate overload resolution inside the computation expression. +/// [] module HighPriority = type TaskSeqBuilder with - member inline Bind: task: Task<'TResult1> * continuation: ('TResult1 -> TaskSeqCode<'T>) -> TaskSeqCode<'T> + member inline Bind: task: Task<'TResult1> * continuation: ('TResult1 -> ResumableTSC<'T>) -> ResumableTSC<'T> From 226652c679a50fa2fbe76a2e01c86d242b457ac3 Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Mon, 28 Nov 2022 03:25:44 +0100 Subject: [PATCH 66/68] Revert changes to `TaskSeq.fsi` --- src/FSharp.Control.TaskSeq/TaskSeq.fsi | 888 ++++++++++++++++--------- 1 file changed, 568 insertions(+), 320 deletions(-) diff --git a/src/FSharp.Control.TaskSeq/TaskSeq.fsi b/src/FSharp.Control.TaskSeq/TaskSeq.fsi index 47f1b1e4..1f0f1497 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeq.fsi +++ b/src/FSharp.Control.TaskSeq/TaskSeq.fsi @@ -1,322 +1,570 @@ namespace FSharp.Control - - module TaskSeq = - - val empty<'T> : System.Collections.Generic.IAsyncEnumerable<'T> - - val singleton: - source: 'T -> System.Collections.Generic.IAsyncEnumerable<'T> - - val isEmpty: source: taskSeq<'T> -> System.Threading.Tasks.Task - - val toList: source: taskSeq<'T> -> 'T list - - val toArray: source: taskSeq<'T> -> 'T[] - - val toSeq: source: taskSeq<'T> -> seq<'T> - - val toArrayAsync: - source: taskSeq<'T> -> System.Threading.Tasks.Task<'T[]> - - val toListAsync: - source: taskSeq<'T> -> System.Threading.Tasks.Task<'T list> - - val toResizeArrayAsync: - source: taskSeq<'T> -> System.Threading.Tasks.Task> - - val toIListAsync: - source: taskSeq<'T> -> - System.Threading.Tasks.Task> - - val ofArray: - source: 'T[] -> System.Collections.Generic.IAsyncEnumerable<'T> - - val ofList: - source: 'T list -> System.Collections.Generic.IAsyncEnumerable<'T> - - val ofSeq: - source: seq<'T> -> System.Collections.Generic.IAsyncEnumerable<'T> - - val ofResizeArray: - source: ResizeArray<'T> -> - System.Collections.Generic.IAsyncEnumerable<'T> - - val ofTaskSeq: - source: seq<#System.Threading.Tasks.Task<'T>> -> - System.Collections.Generic.IAsyncEnumerable<'T> - - val ofTaskList: - source: #System.Threading.Tasks.Task<'T> list -> - System.Collections.Generic.IAsyncEnumerable<'T> - - val ofTaskArray: - source: #System.Threading.Tasks.Task<'T> array -> - System.Collections.Generic.IAsyncEnumerable<'T> - - val ofAsyncSeq: - source: seq> -> - System.Collections.Generic.IAsyncEnumerable<'T> - - val ofAsyncList: - source: Async<'T> list -> - System.Collections.Generic.IAsyncEnumerable<'T> - - val ofAsyncArray: - source: Async<'T> array -> - System.Collections.Generic.IAsyncEnumerable<'T> - - val length: source: taskSeq<'T> -> System.Threading.Tasks.Task - - val lengthOrMax: - max: int -> source: taskSeq<'T> -> System.Threading.Tasks.Task - - val lengthBy: - predicate: ('T -> bool) -> - source: taskSeq<'T> -> System.Threading.Tasks.Task - - val lengthByAsync: - predicate: ('T -> #System.Threading.Tasks.Task) -> - source: taskSeq<'T> -> System.Threading.Tasks.Task - - val init: - count: int -> - initializer: (int -> 'T) -> - System.Collections.Generic.IAsyncEnumerable<'T> - - val initInfinite: - initializer: (int -> 'T) -> - System.Collections.Generic.IAsyncEnumerable<'T> - - val initAsync: - count: int -> - initializer: (int -> #System.Threading.Tasks.Task<'T>) -> - System.Collections.Generic.IAsyncEnumerable<'T> - - val initInfiniteAsync: - initializer: (int -> #System.Threading.Tasks.Task<'T>) -> - System.Collections.Generic.IAsyncEnumerable<'T> - - val delay: - generator: (unit -> taskSeq<'T>) -> - System.Collections.Generic.IAsyncEnumerable<'T> - - val concat: - sources: taskSeq<#taskSeq<'T>> -> - System.Collections.Generic.IAsyncEnumerable<'T> - - val append: - source1: #taskSeq<'T> -> - source2: #taskSeq<'T> -> - System.Collections.Generic.IAsyncEnumerable<'T> - - val appendSeq: - source1: #taskSeq<'T> -> - source2: #seq<'T> -> System.Collections.Generic.IAsyncEnumerable<'T> - - val prependSeq: - source1: #seq<'T> -> - source2: #taskSeq<'T> -> - System.Collections.Generic.IAsyncEnumerable<'T> - - val cast: source: taskSeq -> taskSeq<'T> - - val box: - source: taskSeq<'T> -> - System.Collections.Generic.IAsyncEnumerable - - val unbox: source: taskSeq -> taskSeq<'U> when 'U: struct - - val iter: - action: ('T -> unit) -> - source: taskSeq<'T> -> System.Threading.Tasks.Task - - val iteri: - action: (int -> 'T -> unit) -> - source: taskSeq<'T> -> System.Threading.Tasks.Task - - val iterAsync: - action: ('T -> #System.Threading.Tasks.Task) -> - source: taskSeq<'T> -> System.Threading.Tasks.Task - - val iteriAsync: - action: (int -> 'T -> #System.Threading.Tasks.Task) -> - source: taskSeq<'T> -> System.Threading.Tasks.Task - - val map: - mapper: ('T -> 'U) -> - source: taskSeq<'T> -> - System.Collections.Generic.IAsyncEnumerable<'U> - - val mapi: - mapper: (int -> 'T -> 'U) -> - source: taskSeq<'T> -> - System.Collections.Generic.IAsyncEnumerable<'U> - - val mapAsync: - mapper: ('T -> #System.Threading.Tasks.Task<'U>) -> - source: taskSeq<'T> -> - System.Collections.Generic.IAsyncEnumerable<'U> - - val mapiAsync: - mapper: (int -> 'T -> #System.Threading.Tasks.Task<'U>) -> - source: taskSeq<'T> -> - System.Collections.Generic.IAsyncEnumerable<'U> - - val collect: - binder: ('T -> #System.Collections.Generic.IAsyncEnumerable<'U>) -> - source: taskSeq<'T> -> - System.Collections.Generic.IAsyncEnumerable<'U> - - val collectSeq: - binder: ('T -> #seq<'U>) -> - source: taskSeq<'T> -> - System.Collections.Generic.IAsyncEnumerable<'U> - - val collectAsync: - binder: ('T -> #System.Threading.Tasks.Task<'TSeqU>) -> - source: taskSeq<'T> -> taskSeq<'U> - when 'TSeqU :> System.Collections.Generic.IAsyncEnumerable<'U> - - val collectSeqAsync: - binder: ('T -> #System.Threading.Tasks.Task<'SeqU>) -> - source: taskSeq<'T> -> taskSeq<'U> when 'SeqU :> seq<'U> - - val tryHead: - source: taskSeq<'T> -> System.Threading.Tasks.Task<'T option> - - val head: source: taskSeq<'T> -> System.Threading.Tasks.Task<'T> - - val tryLast: - source: taskSeq<'T> -> System.Threading.Tasks.Task<'T option> - - val last: source: taskSeq<'T> -> System.Threading.Tasks.Task<'T> - - val tryTail: - source: taskSeq<'T> -> - System.Threading.Tasks.Task option> - - val tail: - source: taskSeq<'T> -> - System.Threading.Tasks.Task> - - val tryItem: - index: int -> - source: taskSeq<'T> -> System.Threading.Tasks.Task<'T option> - - val item: - index: int -> source: taskSeq<'T> -> System.Threading.Tasks.Task<'T> - - val tryExactlyOne: - source: taskSeq<'T> -> System.Threading.Tasks.Task<'T option> - - val exactlyOne: source: taskSeq<'T> -> System.Threading.Tasks.Task<'T> - - val indexed: - source: taskSeq<'T> -> - System.Collections.Generic.IAsyncEnumerable - - val choose: - chooser: ('T -> 'U option) -> - source: taskSeq<'T> -> - System.Collections.Generic.IAsyncEnumerable<'U> - - val chooseAsync: - chooser: ('T -> #System.Threading.Tasks.Task<'U option>) -> - source: taskSeq<'T> -> - System.Collections.Generic.IAsyncEnumerable<'U> - - val filter: - predicate: ('T -> bool) -> - source: taskSeq<'T> -> - System.Collections.Generic.IAsyncEnumerable<'T> - - val filterAsync: - predicate: ('T -> #System.Threading.Tasks.Task) -> - source: taskSeq<'T> -> - System.Collections.Generic.IAsyncEnumerable<'T> - - val tryPick: - chooser: ('T -> 'U option) -> - source: taskSeq<'T> -> System.Threading.Tasks.Task<'U option> - - val tryPickAsync: - chooser: ('T -> #System.Threading.Tasks.Task<'U option>) -> - source: taskSeq<'T> -> System.Threading.Tasks.Task<'U option> - - val tryFind: - predicate: ('T -> bool) -> - source: taskSeq<'T> -> System.Threading.Tasks.Task<'T option> - - val tryFindAsync: - predicate: ('T -> #System.Threading.Tasks.Task) -> - source: taskSeq<'T> -> System.Threading.Tasks.Task<'T option> - - val tryFindIndex: - predicate: ('T -> bool) -> - source: taskSeq<'T> -> System.Threading.Tasks.Task - - val tryFindIndexAsync: - predicate: ('T -> #System.Threading.Tasks.Task) -> - source: taskSeq<'T> -> System.Threading.Tasks.Task - - val except: - itemsToExclude: taskSeq<'T> -> - source: taskSeq<'T> -> - System.Collections.Generic.IAsyncEnumerable<'T> when 'T: equality - - val exceptOfSeq: - itemsToExclude: seq<'T> -> - source: taskSeq<'T> -> - System.Collections.Generic.IAsyncEnumerable<'T> when 'T: equality - - val exists: - predicate: ('T -> bool) -> - source: taskSeq<'T> -> System.Threading.Tasks.Task - - val existsAsync: - predicate: ('T -> #System.Threading.Tasks.Task) -> - source: taskSeq<'T> -> System.Threading.Tasks.Task - - val contains: - value: 'T -> source: taskSeq<'T> -> System.Threading.Tasks.Task - when 'T: equality - - val pick: - chooser: ('T -> 'U option) -> - source: taskSeq<'T> -> System.Threading.Tasks.Task<'U> - - val pickAsync: - chooser: ('T -> #System.Threading.Tasks.Task<'U option>) -> - source: taskSeq<'T> -> System.Threading.Tasks.Task<'U> - - val find: - predicate: ('T -> bool) -> - source: taskSeq<'T> -> System.Threading.Tasks.Task<'T> - - val findAsync: - predicate: ('T -> #System.Threading.Tasks.Task) -> - source: taskSeq<'T> -> System.Threading.Tasks.Task<'T> - - val findIndex: - predicate: ('T -> bool) -> - source: taskSeq<'T> -> System.Threading.Tasks.Task - - val findIndexAsync: - predicate: ('T -> #System.Threading.Tasks.Task) -> - source: taskSeq<'T> -> System.Threading.Tasks.Task - - val zip: - source1: taskSeq<'T> -> - source2: taskSeq<'U> -> - System.Collections.Generic.IAsyncEnumerable<'T * 'U> - - val fold: - folder: ('State -> 'T -> 'State) -> - state: 'State -> - source: taskSeq<'T> -> System.Threading.Tasks.Task<'State> - - val foldAsync: - folder: ('State -> 'T -> #System.Threading.Tasks.Task<'State>) -> - state: 'State -> - source: taskSeq<'T> -> System.Threading.Tasks.Task<'State> +#nowarn "1204" + +module TaskSeq = + open System.Collections.Generic + open System.Threading.Tasks + + /// Initialize an empty taskSeq. + val empty<'T> : taskSeq<'T> + + /// + /// Creates a sequence from that generates a single element and then ends. + /// + val singleton: source: 'T -> taskSeq<'T> + + /// + /// Returns if the task sequence contains no elements, otherwise. + /// + val isEmpty: source: taskSeq<'T> -> Task + + /// + /// Returns the length of the sequence. This operation requires the whole sequence to be evaluated and + /// should not be used on potentially infinite sequences, see for an alternative. + /// + val length: source: taskSeq<'T> -> Task + + /// + /// Returns the length of the sequence, or , whichever comes first. This operation requires the task sequence + /// to be evaluated in full, or until items have been processed. Use this method instead of + /// if you want to prevent too many items to be evaluated, or if the sequence is potentially infinite. + /// + val lengthOrMax: max: int -> source: taskSeq<'T> -> Task + + /// + /// Returns the length of the sequence of all items for which the returns true. + /// This operation requires the whole sequence to be evaluated and should not be used on potentially infinite sequences. + /// + val lengthBy: predicate: ('T -> bool) -> source: taskSeq<'T> -> Task + + /// + /// Returns the length of the sequence of all items for which the returns true. + /// This operation requires the whole sequence to be evaluated and should not be used on potentially infinite sequences. + /// If does not need to be asynchronous, consider using . + /// + val lengthByAsync: predicate: ('T -> #Task) -> source: taskSeq<'T> -> Task + + /// + /// Returns a task sequence that is given by the delayed specification of a task sequence. + /// + /// + /// The generating function for the task sequence. + /// The generated task sequence. + val delay: generator: (unit -> taskSeq<'T>) -> taskSeq<'T> + + /// + /// Generates a new task sequence which, when iterated, will return successive elements by calling the given function + /// with the current index, up to the given count. Each element is saved after its initialization for successive access to + /// , which will not re-evaluate the . However, + /// re-iterating the returned task sequence will re-evaluate the initialization function. The returned sequence may + /// be passed between threads safely. However, individual IEnumerator values generated from the returned sequence should + /// not be accessed concurrently. + /// + /// + /// The maximum number of items to generate for the sequence. + /// A function that generates an item in the sequence from a given index. + /// The resulting task sequence. + /// Thrown when count is negative. + val init: count: int -> initializer: (int -> 'T) -> taskSeq<'T> + + /// + /// Generates a new task sequence which, when iterated, will return successive elements by calling the given function + /// with the current index, up to the given count. Each element is saved after its initialization for successive access to + /// , which will not re-evaluate the . However, + /// re-iterating the returned task sequence will re-evaluate the initialization function. The returned sequence may + /// be passed between threads safely. However, individual IEnumerator values generated from the returned sequence should + /// not be accessed concurrently. + /// + /// + /// The maximum number of items to generate for the sequence. + /// A function that generates an item in the sequence from a given index. + /// The resulting task sequence. + /// Thrown when count is negative. + val initAsync: count: int -> initializer: (int -> #Task<'T>) -> taskSeq<'T> + + /// + /// Generates a new task sequence which, when iterated, will return successive elements by calling the given function + /// with the current index, ad infinitum, or until is reached. + /// Each element is saved after its initialization for successive access to + /// , which will not re-evaluate the . However, + /// re-iterating the returned task sequence will re-evaluate the initialization function. The returned sequence may + /// be passed between threads safely. However, individual IEnumerator values generated from the returned sequence should + /// not be accessed concurrently. + /// + /// + /// A function that generates an item in the sequence from a given index. + /// The resulting task sequence. + val initInfinite: initializer: (int -> 'T) -> taskSeq<'T> + + /// + /// Generates a new task sequence which, when iterated, will return successive elements by calling the given function + /// with the current index, ad infinitum, or until is reached. + /// Each element is saved after its initialization for successive access to + /// , which will not re-evaluate the . However, + /// re-iterating the returned task sequence will re-evaluate the initialization function. The returned sequence may + /// be passed between threads safely. However, individual IEnumerator values generated from the returned sequence should + /// not be accessed concurrently. + /// + /// + /// A function that generates an item in the sequence from a given index. + /// The resulting task sequence. + val initInfiniteAsync: initializer: (int -> #Task<'T>) -> taskSeq<'T> + + /// + /// Combines the given task sequence of task sequences and concatenates them end-to-end, to form a + /// new flattened, single task sequence. Each task sequence is awaited item by item, before the next is iterated. + /// + /// + /// The input enumeration-of-enumerations. + /// The resulting task sequence. + /// Thrown when the input sequence is null. + val concat: sources: taskSeq<#taskSeq<'T>> -> taskSeq<'T> + + /// + /// Concatenates task sequences and in order as a single + /// task sequence. + /// + /// + /// The first input task sequence. + /// The second input task sequence. + /// The resulting task sequence. + /// Thrown when either of the input sequences is null. + val append: source1: #taskSeq<'T> -> source2: #taskSeq<'T> -> taskSeq<'T> + + /// + /// Concatenates a task sequence with a non-async F# in + /// and returns a single task sequence. + /// + /// + /// The input task sequence. + /// The input F# sequence. + /// The resulting task sequence. + /// Thrown when either of the input sequences is null. + val appendSeq: source1: #taskSeq<'T> -> source2: #seq<'T> -> taskSeq<'T> + + /// + /// Concatenates a non-async F# in with a task sequence in + /// and returns a single task sequence. + /// + /// + /// The input F# sequence. + /// The input task sequence. + /// The resulting task sequence. + /// Thrown when either of the input sequences is null. + val prependSeq: source1: #seq<'T> -> source2: #taskSeq<'T> -> taskSeq<'T> + + /// Returns taskSeq as an array. This function is blocking until the sequence is exhausted and will properly dispose of the resources. + val toList: source: taskSeq<'T> -> 'T list + + /// Returns taskSeq as an array. This function is blocking until the sequence is exhausted and will properly dispose of the resources. + val toArray: source: taskSeq<'T> -> 'T[] + + /// + /// Returns the task sequence as an F# , that is, an + /// . This function is blocking at each , but otherwise + /// acts as a normal delay-executed sequence. + /// It will then dispose of the resources. + /// + /// + /// The input task sequence. + /// The resulting task sequence. + val toSeq: source: taskSeq<'T> -> seq<'T> + + /// Unwraps the taskSeq as a Task>. This function is non-blocking. + val toArrayAsync: source: taskSeq<'T> -> Task<'T[]> + + /// Unwraps the taskSeq as a Task>. This function is non-blocking. + val toListAsync: source: taskSeq<'T> -> Task<'T list> + + /// Unwraps the taskSeq as a Task>. This function is non-blocking. + val toResizeArrayAsync: source: taskSeq<'T> -> Task> + + /// Unwraps the taskSeq as a Task>. This function is non-blocking. + val toIListAsync: source: taskSeq<'T> -> Task> + + /// Create a taskSeq of an array. + val ofArray: source: 'T[] -> taskSeq<'T> + + /// Create a taskSeq of a list. + val ofList: source: 'T list -> taskSeq<'T> + + /// Create a taskSeq of a seq. + val ofSeq: source: seq<'T> -> taskSeq<'T> + + /// Create a taskSeq of a ResizeArray, aka List. + val ofResizeArray: source: ResizeArray<'T> -> taskSeq<'T> + + /// Create a taskSeq of a sequence of tasks, that may already have hot-started. + val ofTaskSeq: source: seq<#Task<'T>> -> taskSeq<'T> + + /// Create a taskSeq of a list of tasks, that may already have hot-started. + val ofTaskList: source: #Task<'T> list -> taskSeq<'T> + + /// Create a taskSeq of an array of tasks, that may already have hot-started. + val ofTaskArray: source: #Task<'T> array -> taskSeq<'T> + + /// Create a taskSeq of a seq of async. + val ofAsyncSeq: source: seq> -> taskSeq<'T> + + /// Create a taskSeq of a list of async. + val ofAsyncList: source: Async<'T> list -> taskSeq<'T> + + /// Create a taskSeq of an array of async. + val ofAsyncArray: source: Async<'T> array -> taskSeq<'T> + + /// + /// Boxes as type each item in the sequence asynchyronously. + /// + val box: source: taskSeq<'T> -> taskSeq + + /// + /// Unboxes to the target type each item in the sequence asynchyronously. + /// The target type must be a or a built-in value type. + /// + /// Thrown when the function is unable to cast an item to the target type. + val unbox<'U when 'U: struct> : source: taskSeq -> taskSeq<'U> + + /// + /// Casts each item in the untyped sequence asynchyronously. If your types are boxed struct types + /// it is recommended to use instead. + /// + /// Thrown when the function is unable to cast an item to the target type. + val cast: source: taskSeq -> taskSeq<'T> + + /// Iterates over the taskSeq applying the action function to each item. This function is non-blocking + /// exhausts the sequence as soon as the task is evaluated. + val iter: action: ('T -> unit) -> source: taskSeq<'T> -> Task + + /// Iterates over the taskSeq applying the action function to each item. This function is non-blocking, + /// exhausts the sequence as soon as the task is evaluated. + val iteri: action: (int -> 'T -> unit) -> source: taskSeq<'T> -> Task + + /// Iterates over the taskSeq applying the async action to each item. This function is non-blocking + /// exhausts the sequence as soon as the task is evaluated. + val iterAsync: action: ('T -> #Task) -> source: taskSeq<'T> -> Task + + /// Iterates over the taskSeq, applying the async action to each item. This function is non-blocking, + /// exhausts the sequence as soon as the task is evaluated. + val iteriAsync: action: (int -> 'T -> #Task) -> source: taskSeq<'T> -> Task + + /// Maps over the taskSeq, applying the mapper function to each item. This function is non-blocking. + val map: mapper: ('T -> 'U) -> source: taskSeq<'T> -> taskSeq<'U> + + /// + /// Builds a new task sequence whose elements are the corresponding elements of the input task + /// sequence paired with the integer index (from 0) of each element. + /// Does not evaluate the input sequence until requested. + /// + /// The input task sequence. + /// The resulting task sequence of tuples. + /// Thrown when the input sequence is null. + val indexed: source: taskSeq<'T> -> taskSeq + + /// Maps over the taskSeq with an index, applying the mapper function to each item. This function is non-blocking. + val mapi: mapper: (int -> 'T -> 'U) -> source: taskSeq<'T> -> taskSeq<'U> + + /// Maps over the taskSeq, applying the async mapper function to each item. This function is non-blocking. + val mapAsync: mapper: ('T -> #Task<'U>) -> source: taskSeq<'T> -> taskSeq<'U> + + /// Maps over the taskSeq with an index, applying the async mapper function to each item. This function is non-blocking. + val mapiAsync: mapper: (int -> 'T -> #Task<'U>) -> source: taskSeq<'T> -> taskSeq<'U> + + /// Applies the given function to the items in the taskSeq and concatenates all the results in order. + val collect: binder: ('T -> #taskSeq<'U>) -> source: taskSeq<'T> -> taskSeq<'U> + + /// Applies the given function to the items in the taskSeq and concatenates all the results in order. + val collectSeq: binder: ('T -> #seq<'U>) -> source: taskSeq<'T> -> taskSeq<'U> + + /// Applies the given async function to the items in the taskSeq and concatenates all the results in order. + val collectAsync: binder: ('T -> #Task<'TSeqU>) -> source: taskSeq<'T> -> taskSeq<'U> when 'TSeqU :> taskSeq<'U> + + /// Applies the given async function to the items in the taskSeq and concatenates all the results in order. + val collectSeqAsync: binder: ('T -> #Task<'SeqU>) -> source: taskSeq<'T> -> taskSeq<'U> when 'SeqU :> seq<'U> + + /// + /// Returns the first element of the task sequence from , or if the sequence is empty. + /// + val tryHead: source: taskSeq<'T> -> Task<'T option> + + /// + /// Returns the first elementof the task sequence from + /// + /// Thrown when the sequence is empty. + val head: source: taskSeq<'T> -> Task<'T> + + /// + /// Returns the whole task sequence from , minus its first element, or if the sequence is empty. + /// + val tryTail: source: taskSeq<'T> -> Task option> + + /// + /// Returns the whole task sequence from , minus its first element. + /// + /// Thrown when the sequence is empty. + val tail: source: taskSeq<'T> -> Task> + + /// + /// Returns the last element of the task sequence from , or if the sequence is empty. + /// + val tryLast: source: taskSeq<'T> -> Task<'T option> + + /// + /// Returns the last element of the . + /// + /// Thrown when the sequence is empty. + val last: source: taskSeq<'T> -> Task<'T> + + /// + /// Returns the nth element of the , or if the sequence + /// does not contain enough elements, or if is negative. + /// Parameter is zero-based, that is, the value 0 returns the first element. + /// + val tryItem: index: int -> source: taskSeq<'T> -> Task<'T option> + + /// + /// Returns the nth element of the , or if the sequence + /// does not contain enough elements, or if is negative. + /// + /// Thrown when the sequence has insufficient length or + /// is negative. + val item: index: int -> source: taskSeq<'T> -> Task<'T> + + /// + /// Returns the only element of the task sequence, or if the sequence is empty of + /// contains more than one element. + /// + val tryExactlyOne: source: taskSeq<'T> -> Task<'T option> + + /// + /// Returns the only element of the task sequence. + /// + /// Thrown when the input sequence does not contain precisely one element. + val exactlyOne: source: taskSeq<'T> -> Task<'T> + + /// + /// Applies the given function to each element of the task sequence. Returns + /// a sequence comprised of the results "x" for each element where + /// the function returns Some(x). + /// If is asynchronous, consider using . + /// + val choose: chooser: ('T -> 'U option) -> source: taskSeq<'T> -> taskSeq<'U> + + /// + /// Applies the given asynchronous function to each element of the task sequence. Returns + /// a sequence comprised of the results "x" for each element where + /// the function returns . + /// If does not need to be asynchronous, consider using . + /// + val chooseAsync: chooser: ('T -> #Task<'U option>) -> source: taskSeq<'T> -> taskSeq<'U> + + /// + /// Returns a new collection containing only the elements of the collection + /// for which the given function returns . + /// If is asynchronous, consider using . + /// + val filter: predicate: ('T -> bool) -> source: taskSeq<'T> -> taskSeq<'T> + + /// + /// Returns a new collection containing only the elements of the collection + /// for which the given asynchronous function returns . + /// If does not need to be asynchronous, consider using . + /// + val filterAsync: predicate: ('T -> #Task) -> source: taskSeq<'T> -> taskSeq<'T> + + /// + /// Applies the given function to successive elements of the task sequence + /// in , returning the first result where the function returns . + /// If is asynchronous, consider using . + /// + val tryPick: chooser: ('T -> 'U option) -> source: taskSeq<'T> -> Task<'U option> + + /// + /// Applies the given asynchronous function to successive elements of the task sequence + /// in , returning the first result where the function returns . + /// If does not need to be asynchronous, consider using . + /// + val tryPickAsync: chooser: ('T -> #Task<'U option>) -> source: taskSeq<'T> -> Task<'U option> + + /// + /// Returns the first element of the task sequence in for which the given function + /// returns . Returns if no such element exists. + /// If is asynchronous, consider using . + /// + val tryFind: predicate: ('T -> bool) -> source: taskSeq<'T> -> Task<'T option> + + /// + /// Returns the first element of the task sequence in for which the given asynchronous function + /// returns . Returns if no such element exists. + /// If does not need to be asynchronous, consider using . + /// + val tryFindAsync: predicate: ('T -> #Task) -> source: taskSeq<'T> -> Task<'T option> + + /// + /// Returns the index, starting from zero, of the task sequence in for which the given function + /// returns . Returns if no such element exists. + /// If is asynchronous, consider using . + /// + val tryFindIndex: predicate: ('T -> bool) -> source: taskSeq<'T> -> Task + + /// + /// Returns the index, starting from zero, of the task sequence in for which the given asynchronous function + /// returns . Returns if no such element exists. + /// If does not need to be asynchronous, consider using . + /// + val tryFindIndexAsync: predicate: ('T -> #Task) -> source: taskSeq<'T> -> Task + + + /// + /// Applies the given function to successive elements of the task sequence + /// in , returning the first result where the function returns . + /// If is asynchronous, consider using . + /// Thrown when every item of the sequence + /// evaluates to when the given function is applied. + /// + val pick: chooser: ('T -> 'U option) -> source: taskSeq<'T> -> Task<'U> + + /// + /// Applies the given asynchronous function to successive elements of the task sequence + /// in , returning the first result where the function returns . + /// If does not need to be asynchronous, consider using . + /// Thrown when every item of the sequence + /// evaluates to when the given function is applied. + /// + val pickAsync: chooser: ('T -> #Task<'U option>) -> source: taskSeq<'T> -> Task<'U> + + /// + /// Returns the first element of the task sequence in for which the given function + /// returns . + /// If is asynchronous, consider using . + /// + /// Thrown if no element returns when + /// evaluated by the function. + val find: predicate: ('T -> bool) -> source: taskSeq<'T> -> Task<'T> + + /// + /// Returns the first element of the task sequence in for which the given + /// asynchronous function returns . + /// If does not need to be asynchronous, consider using . + /// + /// Thrown if no element returns when + /// evaluated by the function. + val findAsync: predicate: ('T -> #Task) -> source: taskSeq<'T> -> Task<'T> + + /// + /// Returns the index, starting from zero, of the first element of the task sequence in for which + /// the given function returns . + /// If is asynchronous, consider using . + /// + /// Thrown if no element returns when + /// evaluated by the function. + val findIndex: predicate: ('T -> bool) -> source: taskSeq<'T> -> Task + + /// + /// Returns the index, starting from zero, of the task sequence in for which the given + /// asynchronous function returns . + /// If does not need to be asynchronous, consider using . + /// + /// + /// Thrown if no element returns when + /// evaluated by the function. + val findIndexAsync: predicate: ('T -> #Task) -> source: taskSeq<'T> -> Task + + /// + /// Tests if the sequence contains the specified element. Returns + /// if contains the specified element; + /// otherwise. + /// + /// + /// The value to locate in the input sequence. + /// The input sequence. + /// True if the input sequence contains the specified element; false otherwise. + /// Thrown when the input sequence is null. + val contains<'T when 'T: equality> : value: 'T -> source: taskSeq<'T> -> Task + + /// + /// Tests if any element of the task sequence in satisfies + /// the given . + /// The function is applied to the elements of the input sequence. If any application + /// returns then the overall result is and no further elements are evaluated and tested. + /// Otherwise, is returned. + /// + /// + /// A function to test each item of the input sequence. + /// The input sequence. /// + /// True if any result from the predicate is true; false otherwise. /// + /// Thrown when the input sequence is null. + val exists: predicate: ('T -> bool) -> source: taskSeq<'T> -> Task + + /// + /// Tests if any element of the task sequence in satisfies + /// the given async . + /// The function is applied to the elements of the input sequence. If any application + /// returns then the overall result is and no further elements are evaluated and tested. + /// Otherwise, is returned. + /// + /// + /// A function to test each item of the input sequence. + /// The input sequence. /// + /// True if any result from the predicate is true; false otherwise. /// + /// Thrown when the input sequence is null. + val existsAsync: predicate: ('T -> #Task) -> source: taskSeq<'T> -> Task + + /// + /// Returns a new task sequence with the distinct elements of the second task sequence which do not appear in the + /// , using generic hash and equality comparisons to compare values. + /// + /// + /// + /// Note that this function returns a task sequence that digests the whole of the first input task sequence as soon as + /// the result sequence first gets awaited or iterated. As a result this function should not be used with + /// large or infinite sequences in the first parameter. The function makes no assumption on the ordering of the first input + /// sequence. + /// + /// + /// A task sequence whose elements that also occur in the second sequence will cause those elements to be removed from the returned sequence. + /// A sequence whose elements that are not also in first will be returned. + /// A sequence that contains the set difference of the elements of two sequences. + /// + /// Thrown when either of the two input sequences is null. + val except<'T when 'T: equality> : itemsToExclude: taskSeq<'T> -> source: taskSeq<'T> -> taskSeq<'T> + + /// + /// Returns a new task sequence with the distinct elements of the second task sequence which do not appear in the + /// , using generic hash and equality comparisons to compare values. + /// + /// + /// + /// Note that this function returns a task sequence that digests the whole of the first input task sequence as soon as + /// the result sequence first gets awaited or iterated. As a result this function should not be used with + /// large or infinite sequences in the first parameter. The function makes no assumption on the ordering of the first input + /// sequence. + /// + /// + /// A task sequence whose elements that also occur in the second sequence will cause those elements to be removed from the returned sequence. + /// A sequence whose elements that are not also in first will be returned. + /// A sequence that contains the set difference of the elements of two sequences. + /// + /// Thrown when either of the two input sequences is null. + val exceptOfSeq<'T when 'T: equality> : itemsToExclude: seq<'T> -> source: taskSeq<'T> -> taskSeq<'T> + + /// + /// Zips two task sequences, returning a taskSeq of the tuples of each sequence, in order. May raise ArgumentException + /// if the sequences are or unequal length. + /// + /// The sequences have different lengths. + val zip: source1: taskSeq<'T> -> source2: taskSeq<'U> -> taskSeq<'T * 'U> + + /// + /// Applies the function to each element in the task sequence, + /// threading an accumulator argument of type through the computation. + /// If the accumulator function is asynchronous, consider using . + /// + val fold: folder: ('State -> 'T -> 'State) -> state: 'State -> source: taskSeq<'T> -> Task<'State> + + /// + /// Applies the asynchronous function to each element in the task sequence, + /// threading an accumulator argument of type through the computation. + /// If the accumulator function does not need to be asynchronous, consider using . + /// + val foldAsync: folder: ('State -> 'T -> #Task<'State>) -> state: 'State -> source: taskSeq<'T> -> Task<'State> From 6054bd05b099a924267f1b3d5f3f00aa440e3c71 Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Mon, 28 Nov 2022 03:55:22 +0100 Subject: [PATCH 67/68] Update release-notes for PR #111 --- release-notes.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes.txt b/release-notes.txt index 8b5c0802..1b5a945f 100644 --- a/release-notes.txt +++ b/release-notes.txt @@ -2,6 +2,7 @@ Release notes: 0.3.0 (unreleased) + - internal renames, improved doc comments, signature files for complex types, hide internal-only types, #111. - adds support for static TaskLike, allowing the same let! and do! overloads that F# task supports, fixes #110. - implements 'do!' for non-generic Task like with Task.Delay, fixes #43. - adds support for 'for .. in ..' with task sequences in F# tasks and async, #75, #93 and #99 (with help from @theangrybyrd). From dca70479ae6df8c101289b4a33873750d9ef3ecd Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Mon, 28 Nov 2022 04:13:50 +0100 Subject: [PATCH 68/68] Update version to v0.3.0 and update release notes --- Version.props | 2 +- release-notes.txt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Version.props b/Version.props index 7f8337f7..9c576a91 100644 --- a/Version.props +++ b/Version.props @@ -1,5 +1,5 @@ - 0.2.2 + 0.3.0 \ No newline at end of file diff --git a/release-notes.txt b/release-notes.txt index 1b5a945f..d80f41d1 100644 --- a/release-notes.txt +++ b/release-notes.txt @@ -1,8 +1,8 @@ Release notes: -0.3.0 (unreleased) - - internal renames, improved doc comments, signature files for complex types, hide internal-only types, #111. +0.3.0 + - internal renames, improved doc comments, signature files for complex types, hide internal-only types, fixes #112. - adds support for static TaskLike, allowing the same let! and do! overloads that F# task supports, fixes #110. - implements 'do!' for non-generic Task like with Task.Delay, fixes #43. - adds support for 'for .. in ..' with task sequences in F# tasks and async, #75, #93 and #99 (with help from @theangrybyrd).