Skip to content

Commit 988ac7f

Browse files
authored
Merge pull request #235 from fsprojects/improve-takewhile-skipwhile
Improve `takewhile`, `takeWhileInclusive` and `skipwhile`, `skipWhileInclusive`
2 parents 9338100 + 56f4dbe commit 988ac7f

File tree

2 files changed

+54
-125
lines changed

2 files changed

+54
-125
lines changed

src/FSharp.Control.TaskSeq/TaskSeq.fs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -299,14 +299,14 @@ type TaskSeq private () =
299299
static member take count source = Internal.skipOrTake Take count source
300300
static member truncate count source = Internal.skipOrTake Truncate count source
301301

302-
static member takeWhile predicate source = Internal.takeWhile Exclusive (Predicate predicate) source
303-
static member takeWhileAsync predicate source = Internal.takeWhile Exclusive (PredicateAsync predicate) source
304-
static member takeWhileInclusive predicate source = Internal.takeWhile Inclusive (Predicate predicate) source
305-
static member takeWhileInclusiveAsync predicate source = Internal.takeWhile Inclusive (PredicateAsync predicate) source
306-
static member skipWhile predicate source = Internal.skipWhile Exclusive (Predicate predicate) source
307-
static member skipWhileAsync predicate source = Internal.skipWhile Exclusive (PredicateAsync predicate) source
308-
static member skipWhileInclusive predicate source = Internal.skipWhile Inclusive (Predicate predicate) source
309-
static member skipWhileInclusiveAsync predicate source = Internal.skipWhile Inclusive (PredicateAsync predicate) source
302+
static member takeWhile predicate source = Internal.takeWhile false (Predicate predicate) source
303+
static member takeWhileAsync predicate source = Internal.takeWhile false (PredicateAsync predicate) source
304+
static member takeWhileInclusive predicate source = Internal.takeWhile true (Predicate predicate) source
305+
static member takeWhileInclusiveAsync predicate source = Internal.takeWhile true (PredicateAsync predicate) source
306+
static member skipWhile predicate source = Internal.skipWhile false (Predicate predicate) source
307+
static member skipWhileAsync predicate source = Internal.skipWhile false (PredicateAsync predicate) source
308+
static member skipWhileInclusive predicate source = Internal.skipWhile true (Predicate predicate) source
309+
static member skipWhileInclusiveAsync predicate source = Internal.skipWhile true (PredicateAsync predicate) source
310310

311311
static member tryPick chooser source = Internal.tryPick (TryPick chooser) source
312312
static member tryPickAsync chooser source = Internal.tryPick (TryPickAsync chooser) source

src/FSharp.Control.TaskSeq/TaskSeqInternal.fs

Lines changed: 46 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,6 @@ type internal AsyncEnumStatus =
1111
| WithCurrent
1212
| AfterAll
1313

14-
[<Struct>]
15-
type internal WhileKind =
16-
/// The item under test is included (or skipped) even when the predicate returns false
17-
| Inclusive
18-
/// The item under test is always excluded (or not skipped)
19-
| Exclusive
20-
2114
[<Struct>]
2215
type internal TakeOrSkipKind =
2316
/// use the Seq.take semantics, raises exception if not enough elements
@@ -796,140 +789,76 @@ module internal TaskSeqInternal =
796789

797790
}
798791

799-
let takeWhile whileKind predicate (source: TaskSeq<_>) =
792+
let takeWhile isInclusive predicate (source: TaskSeq<_>) =
800793
checkNonNull (nameof source) source
801794

802795
taskSeq {
803796
use e = source.GetAsyncEnumerator CancellationToken.None
804797
let! notEmpty = e.MoveNextAsync()
805-
let mutable more = notEmpty
806-
807-
match whileKind, predicate with
808-
| Exclusive, Predicate predicate -> // takeWhile
809-
while more do
810-
let value = e.Current
811-
more <- predicate value
812-
813-
if more then
814-
// yield ONLY if predicate is true
815-
yield value
816-
let! hasMore = e.MoveNextAsync()
817-
more <- hasMore
798+
let mutable hasMore = notEmpty
818799

819-
| Inclusive, Predicate predicate -> // takeWhileInclusive
820-
while more do
821-
let value = e.Current
822-
more <- predicate value
823-
824-
// yield regardless of result of predicate
825-
yield value
826-
827-
if more then
828-
let! hasMore = e.MoveNextAsync()
829-
more <- hasMore
800+
match predicate with
801+
| Predicate synchronousPredicate ->
802+
while hasMore && synchronousPredicate e.Current do
803+
yield e.Current
804+
let! cont = e.MoveNextAsync()
805+
hasMore <- cont
830806

831-
| Exclusive, PredicateAsync predicate -> // takeWhileAsync
832-
while more do
833-
let value = e.Current
834-
let! passed = predicate value
835-
more <- passed
807+
| PredicateAsync asyncPredicate ->
808+
let mutable predicateHolds = true
836809

837-
if more then
838-
// yield ONLY if predicate is true
839-
yield value
840-
let! hasMore = e.MoveNextAsync()
841-
more <- hasMore
810+
while hasMore && predicateHolds do // TODO: check perf if `while!` is going to be better or equal
811+
let! predicateIsTrue = asyncPredicate e.Current
842812

843-
| Inclusive, PredicateAsync predicate -> // takeWhileInclusiveAsync
844-
while more do
845-
let value = e.Current
846-
let! passed = predicate value
847-
more <- passed
813+
if predicateIsTrue then
814+
yield e.Current
815+
let! cont = e.MoveNextAsync()
816+
hasMore <- cont
848817

849-
// yield regardless of predicate
850-
yield value
818+
predicateHolds <- predicateIsTrue
851819

852-
if more then
853-
let! hasMore = e.MoveNextAsync()
854-
more <- hasMore
820+
// "inclusive" means: always return the item that we pulled, regardless of the result of applying the predicate
821+
// and only stop thereafter. The non-inclusive versions, in contrast, do not return the item under which the predicate is false.
822+
if hasMore && isInclusive then
823+
yield e.Current
855824
}
856825

857-
let skipWhile whileKind predicate (source: TaskSeq<_>) =
826+
let skipWhile isInclusive predicate (source: TaskSeq<_>) =
858827
checkNonNull (nameof source) source
859828

860829
taskSeq {
861830
use e = source.GetAsyncEnumerator CancellationToken.None
862-
let! moveFirst = e.MoveNextAsync()
863-
let mutable more = moveFirst
864-
865-
match whileKind, predicate with
866-
| Exclusive, Predicate predicate -> // skipWhile
867-
while more && predicate e.Current do
868-
let! hasMore = e.MoveNextAsync()
869-
more <- hasMore
870-
871-
if more then
872-
// yield the last one where the predicate was false
873-
// (this ensures we skip 0 or more)
874-
yield e.Current
875-
876-
while! e.MoveNextAsync() do // get the rest
877-
yield e.Current
878-
879-
| Inclusive, Predicate predicate -> // skipWhileInclusive
880-
while more && predicate e.Current do
881-
let! hasMore = e.MoveNextAsync()
882-
more <- hasMore
883-
884-
if more then
885-
// yield the rest (this ensures we skip 1 or more)
886-
while! e.MoveNextAsync() do
887-
yield e.Current
888-
889-
| Exclusive, PredicateAsync predicate -> // skipWhileAsync
890-
let mutable cont = true
891-
892-
if more then
893-
let! hasMore = predicate e.Current
894-
cont <- hasMore
895-
896-
while more && cont do
897-
let! moveNext = e.MoveNextAsync()
898-
899-
if moveNext then
900-
let! hasMore = predicate e.Current
901-
cont <- hasMore
902-
903-
more <- moveNext
904-
905-
if more then
906-
// yield the last one where the predicate was false
907-
// (this ensures we skip 0 or more)
908-
yield e.Current
831+
let! notEmpty = e.MoveNextAsync()
832+
let mutable hasMore = notEmpty
909833

910-
while! e.MoveNextAsync() do // get the rest
911-
yield e.Current
834+
match predicate with
835+
| Predicate synchronousPredicate ->
836+
while hasMore && synchronousPredicate e.Current do
837+
// keep skipping
838+
let! cont = e.MoveNextAsync()
839+
hasMore <- cont
912840

913-
| Inclusive, PredicateAsync predicate -> // skipWhileInclusiveAsync
914-
let mutable cont = true
841+
| PredicateAsync asyncPredicate ->
842+
let mutable predicateHolds = true
915843

916-
if more then
917-
let! hasMore = predicate e.Current
918-
cont <- hasMore
844+
while hasMore && predicateHolds do // TODO: check perf if `while!` is going to be better or equal
845+
let! predicateIsTrue = asyncPredicate e.Current
919846

920-
while more && cont do
921-
let! moveNext = e.MoveNextAsync()
847+
if predicateIsTrue then
848+
// keep skipping
849+
let! cont = e.MoveNextAsync()
850+
hasMore <- cont
922851

923-
if moveNext then
924-
let! hasMore = predicate e.Current
925-
cont <- hasMore
852+
predicateHolds <- predicateIsTrue
926853

927-
more <- moveNext
854+
// "inclusive" means: always skip the item that we pulled, regardless of the result of applying the predicate
855+
// and only stop thereafter. The non-inclusive versions, in contrast, do not skip the item under which the predicate is false.
856+
if hasMore && not isInclusive then
857+
yield e.Current // don't skip, unless inclusive
928858

929-
if more then
930-
// get the rest, this gives 1 or more semantics
931-
while! e.MoveNextAsync() do
932-
yield e.Current
859+
// propagate the rest
860+
while! e.MoveNextAsync() do
861+
yield e.Current
933862
}
934863

935864
// Consider turning using an F# version of this instead?

0 commit comments

Comments
 (0)