Skip to content

Add TaskSeq.tryitem, item, tryLast, last, tryHead, head, tryPick, pick, tryFind, find, choose, filter, isEmpty and more for IAsyncEnumerable<'T> #23

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 20 commits into from
Oct 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
c518d94
Add internals for TaskSeq.tryItem/Last/Head, tryFind/Pick, choose/filter
abelbraaksma Oct 13, 2022
db5df8d
Add public functions TaskSeq.tryHead, head, tryLast, last, tryItem, i…
abelbraaksma Oct 13, 2022
8ef6372
Update signature file with new signatures for tryHead, head, tryLast,…
abelbraaksma Oct 13, 2022
8f101f6
Remove doc comments from implementation file, from now on, we'll just…
abelbraaksma Oct 13, 2022
874adfa
Cleanup FSI signature file
abelbraaksma Oct 13, 2022
19b5ee0
Implement and document TaskSeq.isEmpty and isEmptyAsync, and add tests
abelbraaksma Oct 13, 2022
9a54fa1
Remove isEmptyAsync and make isEmpty itself asynchronous, people that…
abelbraaksma Oct 13, 2022
c3c002b
Implement TaskSeq.tryExactlyOne and exactlyOne as non-blocking task-f…
abelbraaksma Oct 13, 2022
b021f01
Implement TaskSeq.choose(Async), filter(Async), pick(Async), find(Asy…
abelbraaksma Oct 14, 2022
535f21a
Update readme.md with new functions and the updated doc comments
abelbraaksma Oct 14, 2022
a4e6fc3
Add tests for almost all new functions and a few performance smoke-tests
abelbraaksma Oct 14, 2022
f532d23
Rename source files (previous commit already contains the fsproj change)
abelbraaksma Oct 14, 2022
e63d7ae
Add a build status badge
abelbraaksma Oct 14, 2022
38d6824
Get better logging during test run in CI
abelbraaksma Oct 14, 2022
3c7b254
Split workflow in two files, one for test and one for build, update b…
abelbraaksma Oct 14, 2022
78ca565
Add new workflow file to sln
abelbraaksma Oct 14, 2022
40dd31c
Partial implementation of toSeqOfTasks, not sure it's gonna be useful
abelbraaksma Oct 14, 2022
e743e9a
Several attempts at troubleshooting CI issue, see log details and #25
abelbraaksma Oct 14, 2022
be27e9e
Fix tests for now, see #25 for further investigations
abelbraaksma Oct 14, 2022
c2a0657
Enable --blame-hang-timeout on the main branch as well, and improve l…
abelbraaksma Oct 14, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 2 additions & 50 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: ci
name: ci-build

on: [pull_request]

Expand All @@ -18,55 +18,7 @@ jobs:
# build it, test it, pack it
- name: Run dotnet build
run: ./build.cmd

test-release:
name: Test Release Build
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
- name: Run dotnet test - release
run: dotnet test -c Release --logger "trx;LogFileName=test-results-release.trx" .\src\FSharpy.TaskSeq.Test\FSharpy.TaskSeq.Test.fsproj
- name: Publish test results - release
uses: dorny/test-reporter@v1
if: always()
with:
name: Report release tests
# this path glob pattern requires forward slashes!
path: ./src/FSharpy.TaskSeq.Test/TestResults/test-results-release.trx
reporter: dotnet-trx

test-debug:
name: Test Debug Build
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
- name: Run dotnet test - debug
run: dotnet test -c Debug --logger "trx;LogFileName=test-results-debug.trx" .\src\FSharpy.TaskSeq.Test\FSharpy.TaskSeq.Test.fsproj
- name: Publish test results - debug
uses: dorny/test-reporter@v1
if: always()
with:
name: Report debug tests
# this path glob pattern requires forward slashes!
path: ./src/FSharpy.TaskSeq.Test/TestResults/test-results-debug.trx
reporter: dotnet-trx


# deploy:
# name: deploy
# runs-on: ubuntu-latest
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ jobs:
uses: actions/setup-dotnet@v3
# build it, test it, pack it
- name: Run dotnet test - release
run: dotnet test -c Release --logger "trx;LogFileName=test-results-release.trx" .\src\FSharpy.TaskSeq.Test\FSharpy.TaskSeq.Test.fsproj
run: dotnet test -c Release --blame-hang-timeout 15000ms --logger "trx;LogFileName=test-results-release.trx" --logger "console;verbosity=detailed" .\src\FSharpy.TaskSeq.Test\FSharpy.TaskSeq.Test.fsproj
- name: Publish test results - release
uses: dorny/test-reporter@v1
if: always()
Expand All @@ -60,7 +60,7 @@ jobs:
uses: actions/setup-dotnet@v3
# build it, test it, pack it
- name: Run dotnet test - debug
run: dotnet test -c Debug --logger "trx;LogFileName=test-results-debug.trx" .\src\FSharpy.TaskSeq.Test\FSharpy.TaskSeq.Test.fsproj
run: dotnet test -c Debug --blame-hang-timeout 15000ms --logger "trx;LogFileName=test-results-debug.trx" --logger "console;verbosity=detailed" .\src\FSharpy.TaskSeq.Test\FSharpy.TaskSeq.Test.fsproj
- name: Publish test results - debug
uses: dorny/test-reporter@v1
if: always()
Expand Down
53 changes: 53 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
name: ci-test

on: [pull_request]

jobs:
test-release:
name: Test Release Build
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
- name: Run dotnet test - release
run: dotnet test -c Release --blame-hang-timeout 15000ms --logger "trx;LogFileName=test-results-release.trx" --logger "console;verbosity=detailed" .\src\FSharpy.TaskSeq.Test\FSharpy.TaskSeq.Test.fsproj
- name: Publish test results - release
uses: dorny/test-reporter@v1
if: always()
with:
name: Report release tests
# this path glob pattern requires forward slashes!
path: ./src/FSharpy.TaskSeq.Test/TestResults/test-results-release.trx
reporter: dotnet-trx

test-debug:
name: Test Debug Build
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
- name: Run dotnet test - debug
run: dotnet test -c Debug --blame-hang-timeout 15000ms --logger "trx;LogFileName=test-results-debug.trx" --logger "console;verbosity=detailed" .\src\FSharpy.TaskSeq.Test\FSharpy.TaskSeq.Test.fsproj
- name: Publish test results - debug
uses: dorny/test-reporter@v1
if: always()
with:
name: Report debug tests
# this path glob pattern requires forward slashes!
path: ./src/FSharpy.TaskSeq.Test/TestResults/test-results-debug.trx
reporter: dotnet-trx

178 changes: 172 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[![build](https://github.com/abelbraaksma/TaskSeq/actions/workflows/main.yaml/badge.svg)](https://github.com/abelbraaksma/TaskSeq/actions/workflows/main.yaml)
[![test](https://github.com/abelbraaksma/TaskSeq/actions/workflows/test.yaml/badge.svg)](https://github.com/abelbraaksma/TaskSeq/actions/workflows/test.yaml)

# TaskSeq
An implementation [`IAsyncEnumerable<'T>`](https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.iasyncenumerable-1?view=net-7.0) as a `taskSeq` CE for F# with accompanying `TaskSeq` module.

Expand All @@ -24,8 +27,8 @@ Not necessarily in order of importance:

## 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, proper
documentation will be added soon(ish):
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.

```f#
module TaskSeq =
Expand All @@ -36,6 +39,11 @@ module TaskSeq =
/// Initialize an empty taskSeq.
val empty<'T> : taskSeq<'T>

/// <summary>
/// Returns <see cref="true" /> if the task sequence contains no elements, <see cref="false" /> otherwise.
/// </summary>
val isEmpty: taskSeq: taskSeq<'T> -> Task<bool>

/// 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

Expand Down Expand Up @@ -131,13 +139,171 @@ module TaskSeq =
/// 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>

/// <summary>
/// Returns the first element of the <see cref="IAsyncEnumerable" />, or <see cref="None" /> if the sequence is empty.
/// </summary>
/// <exception cref="ArgumentException">Thrown when the sequence is empty.</exception>
val tryHead: taskSeq: taskSeq<'T> -> Task<'T option>

/// <summary>
/// Returns the first element of the <see cref="IAsyncEnumerable" />.
/// </summary>
/// <exception cref="ArgumentException">Thrown when the sequence is empty.</exception>
val head: taskSeq: taskSeq<'T> -> Task<'T>

/// <summary>
/// Returns the last element of the <see cref="IAsyncEnumerable" />, or <see cref="None" /> if the sequence is empty.
/// </summary>
/// <exception cref="ArgumentException">Thrown when the sequence is empty.</exception>
val tryLast: taskSeq: taskSeq<'T> -> Task<'T option>

/// <summary>
/// Returns the last element of the <see cref="IAsyncEnumerable" />.
/// </summary>
/// <exception cref="ArgumentException">Thrown when the sequence is empty.</exception>
val last: taskSeq: taskSeq<'T> -> Task<'T>

/// <summary>
/// Returns the nth element of the <see cref="IAsyncEnumerable" />, or <see cref="None" /> if the sequence
/// does not contain enough elements, or if <paramref name="index" /> is negative.
/// Parameter <paramref name="index" /> is zero-based, that is, the value 0 returns the first element.
/// </summary>
val tryItem: index: int -> taskSeq: taskSeq<'T> -> Task<'T option>

/// <summary>
/// Returns the nth element of the <see cref="IAsyncEnumerable" />, or <see cref="None" /> if the sequence
/// does not contain enough elements, or if <paramref name="index" /> is negative.
/// </summary>
/// <exception cref="ArgumentException">Thrown when the sequence has insufficient length or
/// <paramref name="index" /> is negative.</exception>
val item: index: int -> taskSeq: taskSeq<'T> -> Task<'T>

/// <summary>
/// Returns the only element of the task sequence, or <see cref="None" /> if the sequence is empty of
/// contains more than one element.
/// </summary>
val tryExactlyOne: source: taskSeq<'T> -> Task<'T option>

/// <summary>
/// Returns the only element of the task sequence.
/// </summary>
/// <exception cref="ArgumentException">Thrown when the input sequence does not contain precisely one element.</exception>
val exactlyOne: source: taskSeq<'T> -> Task<'T>

/// <summary>
/// Applies the given function <paramref name="chooser" /> to each element of the task sequence. Returns
/// a sequence comprised of the results "x" for each element where
/// the function returns <c>Some(x)</c>.
/// If <paramref name="chooser" /> is asynchronous, consider using <see cref="TaskSeq.chooseAsync" />.
/// </summary>
val choose: chooser: ('T -> 'U option) -> source: taskSeq<'T> -> taskSeq<'U>

/// <summary>
/// Applies the given asynchronous function <paramref name="chooser" /> to each element of the task sequence. Returns
/// a sequence comprised of the results "x" for each element where
/// the function returns <see cref="Some(x)" />.
/// If <paramref name="chooser" /> does not need to be asynchronous, consider using <see cref="TaskSeq.choose" />.
/// </summary>
val chooseAsync: chooser: ('T -> #Task<'U option>) -> source: taskSeq<'T> -> taskSeq<'U>

/// <summary>
/// Returns a new collection containing only the elements of the collection
/// for which the given <paramref name="predicate" /> function returns <see cref="true" />.
/// If <paramref name="predicate" /> is asynchronous, consider using <see cref="TaskSeq.filterAsync" />.
/// </summary>
val filter: predicate: ('T -> bool) -> source: taskSeq<'T> -> taskSeq<'T>

/// <summary>
/// Returns a new collection containing only the elements of the collection
/// for which the given asynchronous function <paramref name="predicate" /> returns <see cref="true" />.
/// If <paramref name="predicate" /> does not need to be asynchronous, consider using <see cref="TaskSeq.filter" />.
/// </summary>
val filterAsync: predicate: ('T -> #Task<bool>) -> source: taskSeq<'T> -> taskSeq<'T>

/// <summary>
/// Applies the given function <paramref name="chooser" /> to successive elements of the task sequence
/// in <paramref name="source" />, returning the first result where the function returns <see cref="Some(x)" />.
/// If <paramref name="chooser" /> is asynchronous, consider using <see cref="TaskSeq.tryPickAsync" />.
/// </summary>
val tryPick: chooser: ('T -> 'U option) -> source: taskSeq<'T> -> Task<'U option>

/// <summary>
/// Applies the given asynchronous function <paramref name="chooser" /> to successive elements of the task sequence
/// in <paramref name="source" />, returning the first result where the function returns <see cref="Some(x)" />.
/// If <paramref name="chooser" /> does not need to be asynchronous, consider using <see cref="TaskSeq.tryPick" />.
/// </summary>
val tryPickAsync: chooser: ('T -> #Task<'U option>) -> source: taskSeq<'T> -> Task<'U option>

/// <summary>
/// Returns the first element of the task sequence in <paramref name="source" /> for which the given function
/// <paramref name="predicate" /> returns <see cref="true" />. Returns <see cref="None" /> if no such element exists.
/// If <paramref name="predicate" /> is asynchronous, consider using <see cref="TaskSeq.tryFindAsync" />.
/// </summary>
val tryFind: predicate: ('T -> bool) -> source: taskSeq<'T> -> Task<'T option>

/// <summary>
/// Returns the first element of the task sequence in <paramref name="source" /> for which the given asynchronous function
/// <paramref name="predicate" /> returns <see cref="true" />. Returns <see cref="None" /> if no such element exists.
/// If <paramref name="predicate" /> does not need to be asynchronous, consider using <see cref="TaskSeq.tryFind" />.
/// </summary>
val tryFindAsync: predicate: ('T -> #Task<bool>) -> source: taskSeq<'T> -> Task<'T option>


/// <summary>
/// Applies the given function <paramref name="chooser" /> to successive elements of the task sequence
/// in <paramref name="source" />, returning the first result where the function returns <see cref="Some(x)" />.
/// If <paramref name="chooser" /> is asynchronous, consider using <see cref="TaskSeq.pickAsync" />.
/// <exception cref="KeyNotFoundException">Thrown when every item of the sequence
/// evaluates to <see cref="None" /> when the given function is applied.</exception>
/// </summary>
val pick: chooser: ('T -> 'U option) -> source: taskSeq<'T> -> Task<'U>

/// <summary>
/// Applies the given asynchronous function <paramref name="chooser" /> to successive elements of the task sequence
/// in <paramref name="source" />, returning the first result where the function returns <see cref="Some(x)" />.
/// If <paramref name="chooser" /> does not need to be asynchronous, consider using <see cref="TaskSeq.pick" />.
/// <exception cref="KeyNotFoundException">Thrown when every item of the sequence
/// evaluates to <see cref="None" /> when the given function is applied.</exception>
/// </summary>
val pickAsync: chooser: ('T -> #Task<'U option>) -> source: taskSeq<'T> -> Task<'U>

/// <summary>
/// Returns the first element of the task sequence in <paramref name="source" /> for which the given function
/// <paramref name="predicate" /> returns <see cref="true" />.
/// If <paramref name="predicate" /> is asynchronous, consider using <see cref="TaskSeq.findAsync" />.
/// </summary>
/// <exception cref="KeyNotFoundException">Thrown if no element returns <see cref="true" /> when
/// evaluated by the <paramref name="predicate" /> function.</exception>
val find: predicate: ('T -> bool) -> source: taskSeq<'T> -> Task<'T>

/// <summary>
/// Returns the first element of the task sequence in <paramref name="source" /> for which the given
/// asynchronous function <paramref name="predicate" /> returns <see cref="true" />.
/// If <paramref name="predicate" /> does not need to be asynchronous, consider using <see cref="TaskSeq.find" />.
/// </summary>
/// <exception cref="KeyNotFoundException">Thrown if no element returns <see cref="true" /> when
/// evaluated by the <paramref name="predicate" /> function.</exception>
val findAsync: predicate: ('T -> #Task<bool>) -> source: taskSeq<'T> -> Task<'T>

/// <summary>
/// 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.
val zip: taskSeq1: taskSeq<'T> -> taskSeq2: taskSeq<'U> -> taskSeq<'T * 'U>

/// Applies a function to each element of the task sequence, threading an accumulator argument through the computation.
/// </summary>
/// <exception cref="ArgumentException">The sequences have different lengths.</exception>
val zip: taskSeq1: taskSeq<'T> -> taskSeq2: taskSeq<'U> -> IAsyncEnumerable<'T * 'U>

/// <summary>
/// Applies the function <paramref name="folder" /> to each element in the task sequence,
/// threading an accumulator argument of type <paramref name="'State" /> through the computation.
/// If the accumulator function <paramref name="folder" /> is asynchronous, consider using <see cref="TaskSeq.foldAsync" />.
/// </summary>
val fold: folder: ('State -> 'T -> 'State) -> state: 'State -> taskSeq: taskSeq<'T> -> Task<'State>

/// Applies an async function to each element of the task sequence, threading an accumulator argument through the computation.
/// <summary>
/// Applies the asynchronous function <paramref name="folder" /> to each element in the task sequence,
/// threading an accumulator argument of type <paramref name="'State" /> through the computation.
/// If the accumulator function <paramref name="folder" /> does not need to be asynchronous, consider using <see cref="TaskSeq.fold" />.
/// </summary>
val foldAsync: folder: ('State -> 'T -> #Task<'State>) -> state: 'State -> taskSeq: taskSeq<'T> -> Task<'State>

```
8 changes: 8 additions & 0 deletions src/FSharpy.TaskSeq.Test/AssemblyInfo.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace FSharpy.Tests

open System.Runtime.CompilerServices

[<assembly: Xunit.CollectionBehavior(DisableTestParallelization = true)>]
[<assembly: Xunit.TestCaseOrderer("FSharpy.Tests.AlphabeticalOrderer", "FSharpy.TaskSeq.Test")>]

do ()
23 changes: 16 additions & 7 deletions src/FSharpy.TaskSeq.Test/FSharpy.TaskSeq.Test.fsproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
Expand All @@ -8,15 +8,24 @@
</PropertyGroup>

<ItemGroup>
<Compile Include="AssemblyInfo.fs" />
<Compile Include="Nunit.Extensions.fs" />
<Compile Include="TestUtils.fs" />
<Compile Include="TaskSeq.ToXXX.Tests.fs" />
<Compile Include="TaskSeq.OfXXX.Tests.fs" />
<Compile Include="TaskSeq.Iter.Tests.fs" />
<Compile Include="TaskSeq.Map.Tests.fs" />
<Compile Include="TaskSeq.Choose.Tests.fs" />
<Compile Include="TaskSeq.Collect.Tests.fs" />
<Compile Include="TaskSeq.Filter.Tests.fs" />
<Compile Include="TaskSeq.Find.Tests.fs" />
<Compile Include="TaskSeq.Fold.Tests.fs" />
<Compile Include="TaskSeq.Tests.Utility.fs" />
<Compile Include="TaskSeq.Tests.fs" />
<Compile Include="TaskSeq.Head.Tests.fs" />
<Compile Include="TaskSeq.Item.Tests.fs" />
<Compile Include="TaskSeq.Iter.Tests.fs" />
<Compile Include="TaskSeq.Last.Tests.fs" />
<Compile Include="TaskSeq.Map.Tests.fs" />
<Compile Include="TaskSeq.Pick.Tests.fs" />
<Compile Include="TaskSeq.ToXXX.Tests.fs" />
<Compile Include="TaskSeq.OfXXX.Tests.fs" />
<Compile Include="TaskSeq.Tests.Other.fs" />
<Compile Include="TaskSeq.Tests.CE.fs" />
<Compile Include="TaskSeq.PocTests.fs" />
<Compile Include="Program.fs" />
</ItemGroup>
Expand Down
Loading