Reputation: 57159
The following code will not work as I expected it to:
// Gets all or nothing. If predicate is false for at least one item
// in the sequence, returns None. If seq is empty, returns the empty seq.
let tryGetAllOrNone predicate (source: seq<_>) =
let mutable condition = true
let resultSeq =
seq {
use e = source.GetEnumerator()
while condition && e.MoveNext() && (condition <- predicate e.Current; condition) do
yield e.Current
}
if condition then Some resultSeq
else None
The reason is quite clear: a sequence is lazily evaluated, which means that here the if
statement will be evaluated first, returning the sequence. Then, when we consume the resulting sequence we will always get Some
results, until the condition turns false:
// expect: None (predicate is false for the first item)
> [1;2;3] |> tryGetAllOrNone (fun x -> x = 2);;
val it : seq<int> option = Some (seq [])
// expect None (predicate is false for the second item)
> [1;2;3] |> tryGetAllOrNone (fun x -> x = 1);;
val it : seq<int> option = Some (seq [1])
// correct (predicate is always true)
> [1;2;3] |> tryGetAllOrNone (fun x -> x > 0);;
val it : seq<int> option = Some (seq [1; 2; 3])
I might just have to consume the sequence first, i.e. by using [...yield ...]
instead of seq { .. yield ...}
, but maybe there's a simpler solution that retains the laziness (just asking the question makes it sound backwards, so my gut tells me: consume first, right)?
EDIT: thinking about this is a tad longer has me come to the conclusion that what I'm asking is not possible. You cannot first lazily return one after the other from a sequence and then, upon hitting an invalid item, say: "hey, all those items you got thus far, give them back, if one is invalid, all are invalid!".
Leaving it out here nonetheless in case it helps someone else or in case someone has a better idea ;).
Upvotes: 0
Views: 40
Reputation: 243041
You are right that, in general, you will need to iterate over the whole sequence until you can return None
or Some
. If the last element of the sequence does not satsify the condition, then you need to first read all the preceding elements until you know that you have failed.
The only optimization you can do is that you can return None
as soon as you find a first element that does not satisfy the condition. This is slightly better than building the whole sequence and then checking if the condition was false for any of the elemnetns (in that, if an earlier element fails to satisfy the condition, you can return None
sooner). The following implementation does that:
// Gets all or nothing. If predicate is false for at least one item
// in the sequence, returns None. If seq is empty, returns the empty seq.
let tryGetAllOrNone predicate (source: seq<_>) =
let results = ResizeArray<_>()
let mutable failed = false
use e = source.GetEnumerator()
while not failed && e.MoveNext() do
if not (predicate e.Current) then failed <- true
else results.Add(e.Current)
if failed then None
else Some(seq results)
Upvotes: 2