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
2 changes: 2 additions & 0 deletions src/FSharpy.TaskSeq.Test/FSharpy.TaskSeq.Test.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@
<Compile Include="TaskSeq.Choose.Tests.fs" />
<Compile Include="TaskSeq.Collect.Tests.fs" />
<Compile Include="TaskSeq.Concat.Tests.fs" />
<Compile Include="TaskSeq.Contains.Tests.fs" />
<Compile Include="TaskSeq.Empty.Tests.fs" />
<Compile Include="TaskSeq.ExactlyOne.Tests.fs" />
<Compile Include="TaskSeq.Exists.Tests.fs" />
<Compile Include="TaskSeq.Filter.Tests.fs" />
<Compile Include="TaskSeq.FindIndex.Tests.fs" />
<Compile Include="TaskSeq.Find.Tests.fs" />
Expand Down
116 changes: 116 additions & 0 deletions src/FSharpy.TaskSeq.Test/TaskSeq.Contains.Tests.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
module FSharpy.Tests.Contains

open Xunit
open FsUnit.Xunit
open FsToolkit.ErrorHandling

open FSharpy

//
// TaskSeq.contains
//

module EmptySeq =
[<Theory; ClassData(typeof<TestEmptyVariants>)>]
let ``TaskSeq-contains returns false`` variant =
Gen.getEmptyVariant variant
|> TaskSeq.contains 12
|> Task.map (should be False)

module Immutable =
[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
let ``TaskSeq-contains sad path returns false`` variant =
Gen.getSeqImmutable variant
|> TaskSeq.contains 0
|> Task.map (should be False)

[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
let ``TaskSeq-contains happy path middle of seq`` variant =
Gen.getSeqImmutable variant
|> TaskSeq.contains 5
|> Task.map (should be True)

[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
let ``TaskSeq-contains happy path first item of seq`` variant =
Gen.getSeqImmutable variant
|> TaskSeq.contains 1
|> Task.map (should be True)

[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
let ``TaskSeq-contains happy path last item of seq`` variant =
Gen.getSeqImmutable variant
|> TaskSeq.contains 10
|> Task.map (should be True)

module SideEffects =
[<Theory; ClassData(typeof<TestSideEffectTaskSeq>)>]
let ``TaskSeq-contains KeyNotFoundException only sometimes for mutated state`` variant = task {
let ts = Gen.getSeqWithSideEffect variant

// first: false
let! found = TaskSeq.contains 11 ts
found |> should be False

// find again: found now, because of side effects
let! found = TaskSeq.contains 11 ts
found |> should be True

// find once more: false
let! found = TaskSeq.contains 11 ts
found |> should be False
}

[<Fact>]
let ``TaskSeq-contains _specialcase_ prove we don't read past the found item`` () = task {
let mutable i = 0

let ts = taskSeq {
for _ in 0..9 do
i <- i + 1
yield i
}

let! found = ts |> TaskSeq.contains 3
found |> should be True
i |> should equal 3 // only partial evaluation!

// find next item. We do get a new iterator, but mutable state is now starting at '3', so first item now returned is '4'.
let! found = ts |> TaskSeq.contains 4
found |> should be True
i |> should equal 4 // only partial evaluation!
}

[<Fact>]
let ``TaskSeq-contains _specialcase_ prove we don't read past the found item v2`` () = task {
let mutable i = 0

let ts = taskSeq {
yield 42
i <- i + 1
i <- i + 1
}

let! found = ts |> TaskSeq.contains 42
found |> should be True
i |> should equal 0 // because no MoveNext after found item, the last statements are not executed
}

[<Fact>]
let ``TaskSeq-contains _specialcase_ prove statement after yield is not evaluated`` () = task {
let mutable i = 0

let ts = taskSeq {
for _ in 0..9 do
yield i
i <- i + 1
}

let! found = ts |> TaskSeq.contains 0
found |> should be True
i |> should equal 0 // notice that it should be one higher if the statement after 'yield' is evaluated

// find some next item. We do get a new iterator, but mutable state is now starting at '1'
let! found = ts |> TaskSeq.contains 4
found |> should be True
i |> should equal 4 // only partial evaluation!
}
221 changes: 221 additions & 0 deletions src/FSharpy.TaskSeq.Test/TaskSeq.Exists.Tests.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
module FSharpy.Tests.Exists

open Xunit
open FsUnit.Xunit
open FsToolkit.ErrorHandling

open FSharpy

//
// TaskSeq.exists
// TaskSeq.existsAsyncc
//

module EmptySeq =
[<Theory; ClassData(typeof<TestEmptyVariants>)>]
let ``TaskSeq-exists returns false`` variant =
Gen.getEmptyVariant variant
|> TaskSeq.exists ((=) 12)
|> Task.map (should be False)

[<Theory; ClassData(typeof<TestEmptyVariants>)>]
let ``TaskSeq-existsAsync returns false`` variant =
Gen.getEmptyVariant variant
|> TaskSeq.existsAsync (fun x -> task { return x = 12 })
|> Task.map (should be False)

module Immutable =
[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
let ``TaskSeq-exists sad path returns false`` variant =
Gen.getSeqImmutable variant
|> TaskSeq.exists ((=) 0)
|> Task.map (should be False)

[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
let ``TaskSeq-existsAsync sad path return false`` variant =
Gen.getSeqImmutable variant
|> TaskSeq.existsAsync (fun x -> task { return x = 0 })
|> Task.map (should be False)

[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
let ``TaskSeq-exists happy path middle of seq`` variant =
Gen.getSeqImmutable variant
|> TaskSeq.exists (fun x -> x < 6 && x > 4)
|> Task.map (should be True)

[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
let ``TaskSeq-existsAsync happy path middle of seq`` variant =
Gen.getSeqImmutable variant
|> TaskSeq.existsAsync (fun x -> task { return x < 6 && x > 4 })
|> Task.map (should be True)

[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
let ``TaskSeq-exists happy path first item of seq`` variant =
Gen.getSeqImmutable variant
|> TaskSeq.exists ((=) 1)
|> Task.map (should be True)

[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
let ``TaskSeq-existsAsync happy path first item of seq`` variant =
Gen.getSeqImmutable variant
|> TaskSeq.existsAsync (fun x -> task { return x = 1 })
|> Task.map (should be True)

[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
let ``TaskSeq-exists happy path last item of seq`` variant =
Gen.getSeqImmutable variant
|> TaskSeq.exists ((=) 10)
|> Task.map (should be True)

[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
let ``TaskSeq-existsAsync happy path last item of seq`` variant =
Gen.getSeqImmutable variant
|> TaskSeq.existsAsync (fun x -> task { return x = 10 })
|> Task.map (should be True)

module SideEffects =
[<Theory; ClassData(typeof<TestSideEffectTaskSeq>)>]
let ``TaskSeq-exists KeyNotFoundException only sometimes for mutated state`` variant = task {
let ts = Gen.getSeqWithSideEffect variant
let finder = (=) 11

// first: false
let! found = TaskSeq.exists finder ts
found |> should be False

// find again: found now, because of side effects
let! found = TaskSeq.exists finder ts
found |> should be True

// find once more: false
let! found = TaskSeq.exists finder ts
found |> should be False
}

[<Theory; ClassData(typeof<TestSideEffectTaskSeq>)>]
let ``TaskSeq-existsAsync KeyNotFoundException only sometimes for mutated state`` variant = task {
let ts = Gen.getSeqWithSideEffect variant
let finder x = task { return x = 11 }

// first: false
let! found = TaskSeq.existsAsync finder ts
found |> should be False

// find again: found now, because of side effects
let! found = TaskSeq.existsAsync finder ts
found |> should be True

// find once more: false
let! found = TaskSeq.existsAsync finder ts
found |> should be False
}

[<Fact>]
let ``TaskSeq-exists _specialcase_ prove we don't read past the found item`` () = task {
let mutable i = 0

let ts = taskSeq {
for _ in 0..9 do
i <- i + 1
yield i
}

let! found = ts |> TaskSeq.exists ((=) 3)
found |> should be True
i |> should equal 3 // only partial evaluation!

// find next item. We do get a new iterator, but mutable state is now starting at '3', so first item now returned is '4'.
let! found = ts |> TaskSeq.exists ((=) 4)
found |> should be True
i |> should equal 4 // only partial evaluation!
}

[<Fact>]
let ``TaskSeq-existsAsync _specialcase_ prove we don't read past the found item`` () = task {
let mutable i = 0

let ts = taskSeq {
for _ in 0..9 do
i <- i + 1
yield i
}

let! found = ts |> TaskSeq.existsAsync (fun x -> task { return x = 3 })
found |> should be True
i |> should equal 3 // only partial evaluation!

// find next item. We do get a new iterator, but mutable state is now starting at '3', so first item now returned is '4'.
let! found = ts |> TaskSeq.existsAsync (fun x -> task { return x = 4 })
found |> should be True
i |> should equal 4
}

[<Fact>]
let ``TaskSeq-exists _specialcase_ prove we don't read past the found item v2`` () = task {
let mutable i = 0

let ts = taskSeq {
yield 42
i <- i + 1
i <- i + 1
}

let! found = ts |> TaskSeq.exists ((=) 42)
found |> should be True
i |> should equal 0 // because no MoveNext after found item, the last statements are not executed
}

[<Fact>]
let ``TaskSeq-existsAsync _specialcase_ prove we don't read past the found item v2`` () = task {
let mutable i = 0

let ts = taskSeq {
yield 42
i <- i + 1
i <- i + 1
}

let! found = ts |> TaskSeq.existsAsync (fun x -> task { return x = 42 })
found |> should be True
i |> should equal 0 // because no MoveNext after found item, the last statements are not executed
}

[<Fact>]
let ``TaskSeq-exists _specialcase_ prove statement after yield is not evaluated`` () = task {
let mutable i = 0

let ts = taskSeq {
for _ in 0..9 do
yield i
i <- i + 1
}

let! found = ts |> TaskSeq.exists ((=) 0)
found |> should be True
i |> should equal 0 // notice that it should be one higher if the statement after 'yield' is evaluated

// find some next item. We do get a new iterator, but mutable state is now starting at '1'
let! found = ts |> TaskSeq.exists ((=) 4)
found |> should be True
i |> should equal 4 // only partial evaluation!
}

[<Fact>]
let ``TaskSeq-existsAsync _specialcase_ prove statement after yield is not evaluated`` () = task {
let mutable i = 0

let ts = taskSeq {
for _ in 0..9 do
yield i
i <- i + 1
}

let! found = ts |> TaskSeq.existsAsync (fun x -> task { return x = 0 })
found |> should be True
i |> should equal 0 // notice that it should be one higher if the statement after 'yield' is evaluated

// find some next item. We do get a new iterator, but mutable state is now starting at '1'
let! found = ts |> TaskSeq.existsAsync (fun x -> task { return x = 4 })
found |> should be True
i |> should equal 4 // only partial evaluation!
}
12 changes: 12 additions & 0 deletions src/FSharpy.TaskSeq/TaskSeq.fs
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,18 @@ module TaskSeq =
let tryFindIndex predicate source = Internal.tryFindIndex (Predicate predicate) source
let tryFindIndexAsync predicate source = Internal.tryFindIndex (PredicateAsync predicate) source

let exists predicate source =
Internal.tryFind (Predicate predicate) source
|> Task.map (Option.isSome)

let existsAsync predicate source =
Internal.tryFind (PredicateAsync predicate) source
|> Task.map (Option.isSome)

let contains value source =
Internal.tryFind (Predicate((=) value)) source
|> Task.map (Option.isSome)

let pick chooser source = task {
match! Internal.tryPick (TryPick chooser) source with
| Some item -> return item
Expand Down
Loading