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
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
<Compile Include="TaskSeq.Realworld.fs" />
<Compile Include="TaskSeq.AsyncExtensions.Tests.fs" />
<Compile Include="TaskSeq.TaskExtensions.Tests.fs" />
<Compile Include="TaskSeq.Using.Tests.fs" />
<Compile Include="Program.fs" />
</ItemGroup>

Expand Down
106 changes: 106 additions & 0 deletions src/FSharp.Control.TaskSeq.Test/TaskSeq.Using.Tests.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
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(disposed: bool ref) =
inherit OneGetter()

interface IDisposable with
member _.Dispose() = disposed.Value <- true

type private AsyncDisposable(disposed: bool ref) =
inherit OneGetter()

interface IAsyncDisposable with
member _.DisposeAsync() = ValueTask(task { do disposed.Value <- true })

type private MultiDispose(disposed: int ref) =
inherit OneGetter()

interface IDisposable with
member _.Dispose() = disposed.Value <- 1

interface IAsyncDisposable with
member _.DisposeAsync() = ValueTask(task { do disposed.Value <- -1 })

let private check = TaskSeq.length >> Task.map (should equal 1)

[<Fact>]
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)

[<Fact>]
let ``CE task: Using when type implements IAsyncDisposable`` () =
let disposed = ref false

let ts = taskSeq {
use x = AsyncDisposable(disposed)
yield x.Get1()
}

check ts
|> Task.map (fun _ -> disposed.Value |> should be True)

[<Fact>]
let ``CE task: Using when type implements IDisposable and IAsyncDisposable`` () =
let disposed = ref 0

let ts = taskSeq {
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) // should prefer IAsyncDisposable, which returns -1

[<Fact>]
let ``CE task: Using! when type implements IDisposable`` () =
let disposed = ref false

let ts = taskSeq {
use! x = task { return new Disposable(disposed) }
yield x.Get1()
}

check ts
|> Task.map (fun _ -> disposed.Value |> should be True)

[<Fact>]
let ``CE task: Using! when type implements IAsyncDisposable`` () =
let disposed = ref false

let ts = taskSeq {
use! x = task { return AsyncDisposable(disposed) }
yield x.Get1()
}

check ts
|> Task.map (fun _ -> disposed.Value |> should be True)

[<Fact>]
let ``CE task: Using! when type implements IDisposable and IAsyncDisposable`` () =
let disposed = ref 0

let ts = taskSeq {
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) // should prefer IAsyncDisposable, which returns -1
2 changes: 0 additions & 2 deletions src/FSharp.Control.TaskSeq/AsyncExtensions.fs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
namespace FSharp.Control

open FSharp.Control.TaskSeqBuilders

[<AutoOpen>]
module AsyncExtensions =

Expand Down
1 change: 0 additions & 1 deletion src/FSharp.Control.TaskSeq/AsyncExtensions.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ namespace FSharp.Control

[<AutoOpen>]
module AsyncExtensions =
open FSharp.Control.TaskSeqBuilders

type AsyncBuilder with

Expand Down
9 changes: 5 additions & 4 deletions src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@ Generates optimized IL code through the new resumable state machines, and comes
<PackageReleaseNotes>
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.
Expand Down
2 changes: 0 additions & 2 deletions src/FSharp.Control.TaskSeq/TaskExtensions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 0 additions & 1 deletion src/FSharp.Control.TaskSeq/TaskExtensions.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ namespace FSharp.Control

[<AutoOpen>]
module TaskExtensions =
open FSharp.Control.TaskSeqBuilders

type TaskBuilder with

Expand Down
2 changes: 0 additions & 2 deletions src/FSharp.Control.TaskSeq/TaskSeq.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion src/FSharp.Control.TaskSeq/TaskSeq.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
114 changes: 56 additions & 58 deletions src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace FSharp.Control.TaskSeqBuilders
namespace FSharp.Control

open System.Diagnostics

Expand Down Expand Up @@ -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

[<NoComparison; NoEquality>]
type TaskSeqStateMachineData<'T>() =
Expand Down Expand Up @@ -489,34 +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

// A using statement is just a try/finally with the finally block disposing if non-null.
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.Dispose())
)

member inline this.Using
(
disp: #IAsyncDisposable,
body: #IAsyncDisposable -> TaskSeqCode<'T>,
?priority: IPriority1
) : TaskSeqCode<'T> =

// FIXME: what about priorities?
ignore priority
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(
Expand All @@ -528,23 +494,6 @@ type TaskSeqBuilder() =
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(
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.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 ->
// This will yield with __stack_fin = false
Expand All @@ -556,11 +505,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> =
TaskSeqCode<'T>(fun sm ->
let mutable awaiter = task.GetAwaiter()
Expand Down Expand Up @@ -610,3 +554,57 @@ 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).
//

[<AutoOpen>]
module MediumPriority =
type TaskSeqBuilder with

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(
(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))))
)

member inline this.YieldFrom(source: seq<'T>) : TaskSeqCode<'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 ->
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)))
1 change: 0 additions & 1 deletion src/FSharp.Control.TaskSeq/TaskSeqInternal.fs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ open System
open System.Collections.Generic
open System.Threading
open System.Threading.Tasks
open FSharp.Control.TaskSeqBuilders

[<AutoOpen>]
module ExtraTaskSeqOperators =
Expand Down