Description
While most of the above have trivial semantics and should sometimes take an Async
overload (i.e., findIndexAsync
and initAsync
for the lambda), I'm not entirely sure of the usefulness of TaskSeq.cache
(and we already have TaskSeq.toSeqCached
), but perhaps for parity it should be included.
A note on TaskSeq.cast
vs Seq.cast
While the docs on Seq.cast
say that it is meant for a "loosely typed sequence", to be cast to a generically strongly typed sequence, it can be used just fine to cast an F# seq
, because it also implements IEnumerable
(i.e., the non-generic variant).
// this is fine
let x: seq<uint> = seq { 1 } |> Seq.cast
But F# also allows you to perform an illegal cast:
// this won't give any warnings, but will throw an exception
let x: seq<Guid> = seq { 1 } |> Seq.cast
Since IAsyncEnumerable<'T>
only exists as a typed sequence, we should honor that and only allow valid casts. However, F# doesn't allow a constraint like 'T :> 'U
, the rh-side must be a concrete type. This means that in the end, it'll effectively work the same way as for Seq.cast
through boxing. Alternatively, if the type-relation is known ahead of time, users should probably just write TaskSeq.map (fun x -> x :> _)
just as they would for seq<_>
.
// this is fine
let x: seq<uint> = taskSeq { 1 } |> TaskSeq.cast
// this *should* raise a compile-time exception, but there's no way to enforce that
let x: seq<Guid> = taskSeq { 1 } |> TaskSeq.cast // incompatible
For comparison, Linq.Enumerable.Cast
works through the untyped Enumerable
as well.
EDIT: to get better parity with Seq.cast
and Linq.Enumerable.Cast
, I decided to only allow it to work with untyped task sequences (that is: IAsyncEnumerable<obj>
). I've updated the signature below. In addition, we'll be adding a box
and unbox
helper as well, the latter only for value types. If users want a reinterpret_cast
style cast, they can use TaskSeq.box >> TaskSeq.cast
. Due to the static way this is compiled, this won't add overhead.
TODO list:
-
length
, see Increase surface area: TaskSeq.length, lengthBy and lengthByAsync #53 -
lengthBy
, see Increase surface area: TaskSeq.length, lengthBy and lengthByAsync #53 -
lengthByAsync
, see Increase surface area: TaskSeq.length, lengthBy and lengthByAsync #53 -
allPairs
// later, requirescache
to be implemented -
indexed
, see AddTaskSeq.indexed
,findIndex
,tryFindIndex
,findIndexAsync
,tryFindIndexAsync
#68 -
cache
// later, see MBP approach of AsyncSeq -
cast
, see Add more surface area functions:TaskSeq.cast
,box
andunbox
#67 -
box
, see Add more surface area functions:TaskSeq.cast
,box
andunbox
#67 -
unbox
, see Add more surface area functions:TaskSeq.cast
,box
andunbox
#67 -
concat
, see ImplementTaskSeq.init
,initAsync
,initInfinite
,initInfiniteAsync
andTaskSeq.concat
#69 -
findIndex
, see AddTaskSeq.indexed
,findIndex
,tryFindIndex
,findIndexAsync
,tryFindIndexAsync
#68 -
findIndexAsync
, see AddTaskSeq.indexed
,findIndex
,tryFindIndex
,findIndexAsync
,tryFindIndexAsync
#68 -
tryFindIndex
, see AddTaskSeq.indexed
,findIndex
,tryFindIndex
,findIndexAsync
,tryFindIndexAsync
#68 -
tryFindIndexAsync
, see AddTaskSeq.indexed
,findIndex
,tryFindIndex
,findIndexAsync
,tryFindIndexAsync
#68 -
contains
-
exists
-
existsAsync
-
init
, see ImplementTaskSeq.init
,initAsync
,initInfinite
,initInfiniteAsync
andTaskSeq.concat
#69 -
initAsync
, see ImplementTaskSeq.init
,initAsync
,initInfinite
,initInfiniteAsync
andTaskSeq.concat
#69 -
initInifinite
, see ImplementTaskSeq.init
,initAsync
,initInfinite
,initInfiniteAsync
andTaskSeq.concat
#69 -
initInifiniteAsync
, see ImplementTaskSeq.init
,initAsync
,initInfinite
,initInfiniteAsync
andTaskSeq.concat
#69
Proposals of signatures:
module TaskSeq =
val length: source: taskSeq<'T> -> Task<int>
val lengthBy: predicate: ('T -> bool) -> source: taskSeq<'T> -> Task<int>
val lengthByAsync: predicate: ('T -> #Task<bool>) -> source: taskSeq<'T> -> Task<int>
val allPairs: source1: taskSeq<'T> -> source2: taskSeq<'U> -> taskSeq<'T * 'U>
val indexed: source: taskSeq<'T> -> taskSeq<int * 'T>
val cache: source: taskSeq<'T> -> taskSeq<'T> // later, see MBP approach of AsyncSeq
val cast: source: taskSeq<obj> -> taskSeq<'U>
val box: source: taskSeq<'T> -> taskSeq<obj>
val unbox<'T when 'T: struct> : source: taskSeq<obj> -> taskSeq<'U>
val concat: source1: taskSeq<'T> -> source2: taskSeq<'T> -> taskSeq<'T>
val findIndex: predicate: ('T -> bool) -> source: taskSeq<'T> -> Task<int>
val findIndexAsync: predicate: ('T -> Task<bool>) -> source: taskSeq<'T> -> Task<int>
val tryFindIndex: predicate: ('T -> bool) -> source: taskSeq<'T> -> Task<int option>
val tryFindIndexAsync: predicate: ('T -> Task<bool>) -> source: taskSeq<'T> -> Task<int option>
val contains: value: 'T -> source: taskSeq<'T> -> Task<bool> (requires comparison)
val exists: predicate: ('T -> bool) -> source: taskSeq<'T> -> Task<bool>
val existsAsync: predicate: ('T -> Task<bool>) -> source: taskSeq<'T> -> Task<bool>
val init: count: int -> initializer: (int -> 'T) -> taskSeq<'T>
val initAsync: count: int -> initializer: (int -> Task<'T>) -> taskSeq<'T>
val initInfinite: initializer: (int -> 'T) -> taskSeq<'T>
val initInfiniteAsync: initializer: (int -> Task<'T>) -> taskSeq<'T>