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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 16 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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<string>
let allFilesAsLines() = taskSeq {
let files = Directory.EnumerateFiles(@"c:\temp")
Expand Down Expand Up @@ -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]
Expand Down
22 changes: 16 additions & 6 deletions assets/nuget-package-readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

-----------------------------------------
Expand All @@ -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<string>
let allFilesAsLines() = taskSeq {
let files = Directory.EnumerateFiles(@"c:\temp")
Expand Down Expand Up @@ -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]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@
<Compile Include="TaskSeq.StateTransitionBug-delayed.Tests.CE.fs" />
<Compile Include="TaskSeq.PocTests.fs" />
<Compile Include="TaskSeq.Realworld.fs" />
<Compile Include="TaskSeq.AsyncExtensions.Tests.fs" />
<Compile Include="TaskSeq.TaskExtensions.Tests.fs" />
<Compile Include="Program.fs" />
</ItemGroup>

Expand Down
144 changes: 144 additions & 0 deletions src/FSharp.Control.TaskSeq.Test/TaskSeq.AsyncExtensions.Tests.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
module TaskSeq.Tests.AsyncExtensions

open System
open Xunit
open FsUnit.Xunit

open FSharp.Control

//
// Async extensions
//

module EmptySeq =
[<Theory; ClassData(typeof<TestEmptyVariants>)>]
let ``Async-for CE with empty taskSeq`` variant = async {
let values = Gen.getEmptyVariant variant

let mutable sum = 42

for x in values do
sum <- sum + x

sum |> should equal 42
}

[<Fact>]
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 =
[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
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
}

[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
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
}

[<Fact>]
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 =
[<Theory; ClassData(typeof<TestSideEffectTaskSeq>)>]
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
}

[<Theory; ClassData(typeof<TestSideEffectTaskSeq>)>]
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]
}

module Other =
[<Fact>]
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
}

[<Fact>]
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
}
Loading