diff --git a/src/FSharpy.TaskSeq.Test/FSharpy.TaskSeq.Test.fsproj b/src/FSharpy.TaskSeq.Test/FSharpy.TaskSeq.Test.fsproj
index d67231ca..92376960 100644
--- a/src/FSharpy.TaskSeq.Test/FSharpy.TaskSeq.Test.fsproj
+++ b/src/FSharpy.TaskSeq.Test/FSharpy.TaskSeq.Test.fsproj
@@ -12,6 +12,7 @@
+
diff --git a/src/FSharpy.TaskSeq.Test/TaskSeq.Cast.Tests.fs b/src/FSharpy.TaskSeq.Test/TaskSeq.Cast.Tests.fs
new file mode 100644
index 00000000..334ed685
--- /dev/null
+++ b/src/FSharpy.TaskSeq.Test/TaskSeq.Cast.Tests.fs
@@ -0,0 +1,183 @@
+module FSharpy.Tests.Cast
+
+open System
+
+open Xunit
+open FsUnit.Xunit
+open FsToolkit.ErrorHandling
+
+open FSharpy
+
+//
+// TaskSeq.box
+// TaskSeq.unbox
+// TaskSeq.cast
+//
+
+/// Asserts that a sequence contains the char values 'A'..'J'.
+let validateSequence ts =
+ ts
+ |> TaskSeq.toSeqCachedAsync
+ |> Task.map (Seq.map string)
+ |> Task.map (String.concat "")
+ |> Task.map (should equal "12345678910")
+
+module EmptySeq =
+ [)>]
+ let ``TaskSeq-box empty`` variant = Gen.getEmptyVariant variant |> TaskSeq.box |> verifyEmpty
+
+ [)>]
+ let ``TaskSeq-unbox empty`` variant =
+ Gen.getEmptyVariant variant
+ |> TaskSeq.box
+ |> TaskSeq.unbox
+ |> verifyEmpty
+
+ [)>]
+ let ``TaskSeq-cast empty`` variant =
+ Gen.getEmptyVariant variant
+ |> TaskSeq.box
+ |> TaskSeq.cast
+ |> verifyEmpty
+
+ [)>]
+ let ``TaskSeq-unbox empty to invalid type should not fail`` variant =
+ Gen.getEmptyVariant variant
+ |> TaskSeq.box
+ |> TaskSeq.unbox // cannot cast to int, but for empty sequences, the exception won't be thrown
+ |> verifyEmpty
+
+ [)>]
+ let ``TaskSeq-cast empty to invalid type should not fail`` variant =
+ Gen.getEmptyVariant variant
+ |> TaskSeq.box
+ |> TaskSeq.cast // cannot cast to int, but for empty sequences, the exception won't be thrown
+ |> verifyEmpty
+
+module Immutable =
+ [)>]
+ let ``TaskSeq-box`` variant =
+ Gen.getSeqImmutable variant
+ |> TaskSeq.box
+ |> validateSequence
+
+ [)>]
+ let ``TaskSeq-unbox`` variant =
+ Gen.getSeqImmutable variant
+ |> TaskSeq.box
+ |> TaskSeq.unbox
+ |> validateSequence
+
+ [)>]
+ let ``TaskSeq-cast`` variant =
+ Gen.getSeqImmutable variant
+ |> TaskSeq.box
+ |> TaskSeq.cast
+ |> validateSequence
+
+ [)>]
+ let ``TaskSeq-unbox invalid type should throw`` variant =
+ fun () ->
+ Gen.getSeqImmutable variant
+ |> TaskSeq.box
+ |> TaskSeq.unbox // cannot unbox from int to uint, even though types have the same size
+ |> TaskSeq.toArrayAsync
+ |> Task.ignore
+
+ |> should throwAsyncExact typeof
+
+ [)>]
+ let ``TaskSeq-cast invalid type should throw`` variant =
+ fun () ->
+ Gen.getSeqImmutable variant
+ |> TaskSeq.box
+ |> TaskSeq.cast
+ |> TaskSeq.toArrayAsync
+ |> Task.ignore
+
+ |> should throwAsyncExact typeof
+
+ [)>]
+ let ``TaskSeq-unbox invalid type should NOT throw before sequence is iterated`` variant =
+ fun () ->
+ Gen.getSeqImmutable variant
+ |> TaskSeq.box
+ |> TaskSeq.unbox // no iteration done
+ |> ignore
+
+ |> should not' (throw typeof)
+
+ [)>]
+ let ``TaskSeq-cast invalid type should NOT throw before sequence is iterated`` variant =
+ fun () ->
+ Gen.getSeqImmutable variant
+ |> TaskSeq.box
+ |> TaskSeq.cast // no iteration done
+ |> ignore
+
+ |> should not' (throw typeof)
+
+module SideEffects =
+ []
+ let ``TaskSeq-box prove that it has no effect until executed`` () =
+ let mutable i = 0
+
+ let ts = taskSeq {
+ i <- i + 1 // we should not get here
+ i <- i + 1
+ yield 42
+ i <- i + 1
+ }
+
+ // point of this test: just calling 'box' won't execute anything of the sequence!
+ let boxed = ts |> TaskSeq.box |> TaskSeq.box |> TaskSeq.box
+
+ // no side effect until iterated
+ i |> should equal 0
+
+ boxed
+ |> TaskSeq.last
+ |> Task.map (should equal 42)
+ |> Task.map (fun () -> i = 9)
+
+ []
+ let ``TaskSeq-unbox prove that it has no effect until executed`` () =
+ let mutable i = 0
+
+ let ts = taskSeq {
+ i <- i + 1 // we should not get here
+ i <- i + 1
+ yield box 42
+ i <- i + 1
+ }
+
+ // point of this test: just calling 'unbox' won't execute anything of the sequence!
+ let unboxed = ts |> TaskSeq.unbox
+
+ // no side effect until iterated
+ i |> should equal 0
+
+ unboxed
+ |> TaskSeq.last
+ |> Task.map (should equal 42)
+ |> Task.map (fun () -> i = 3)
+
+ []
+ let ``TaskSeq-cast prove that it has no effect until executed`` () =
+ let mutable i = 0
+
+ let ts = taskSeq {
+ i <- i + 1 // we should not get here
+ i <- i + 1
+ yield box 42
+ i <- i + 1
+ }
+
+ // point of this test: just calling 'cast' won't execute anything of the sequence!
+ let cast = ts |> TaskSeq.cast
+ i |> should equal 0 // no side effect until iterated
+
+ cast
+ |> TaskSeq.last
+ |> Task.map (should equal 42)
+ |> Task.map (fun () -> i = 3)
diff --git a/src/FSharpy.TaskSeq.Test/TaskSeq.Map.Tests.fs b/src/FSharpy.TaskSeq.Test/TaskSeq.Map.Tests.fs
index f9dc6a3a..4b91f58c 100644
--- a/src/FSharpy.TaskSeq.Test/TaskSeq.Map.Tests.fs
+++ b/src/FSharpy.TaskSeq.Test/TaskSeq.Map.Tests.fs
@@ -36,25 +36,25 @@ let validateSequenceWithOffset offset ts =
module EmptySeq =
[)>]
- let ``TaskSeq-map maps in correct order`` variant =
+ let ``TaskSeq-map empty`` variant =
Gen.getEmptyVariant variant
|> TaskSeq.map (fun item -> char (item + 64))
|> verifyEmpty
[)>]
- let ``TaskSeq-mapi maps in correct order`` variant =
+ let ``TaskSeq-mapi empty`` variant =
Gen.getEmptyVariant variant
|> TaskSeq.mapi (fun i _ -> char (i + 65))
|> verifyEmpty
[)>]
- let ``TaskSeq-mapAsync maps in correct order`` variant =
+ let ``TaskSeq-mapAsync empty`` variant =
Gen.getEmptyVariant variant
|> TaskSeq.mapAsync (fun item -> task { return char (item + 64) })
|> verifyEmpty
[)>]
- let ``TaskSeq-mapiAsync maps in correct order`` variant =
+ let ``TaskSeq-mapiAsync empty`` variant =
Gen.getEmptyVariant variant
|> TaskSeq.mapiAsync (fun i _ -> task { return char (i + 65) })
|> verifyEmpty
diff --git a/src/FSharpy.TaskSeq/TaskSeq.fs b/src/FSharpy.TaskSeq/TaskSeq.fs
index f815a750..9e47f186 100644
--- a/src/FSharpy.TaskSeq/TaskSeq.fs
+++ b/src/FSharpy.TaskSeq/TaskSeq.fs
@@ -163,6 +163,9 @@ module TaskSeq =
// iter/map/collect functions
//
+ let cast source : taskSeq<'T> = Internal.map (SimpleAction(fun (x: obj) -> x :?> 'T)) source
+ let box source = Internal.map (SimpleAction(fun x -> box x)) source
+ let unbox<'U when 'U: struct> (source: taskSeq) : taskSeq<'U> = Internal.map (SimpleAction(fun x -> unbox x)) source
let iter action source = Internal.iter (SimpleAction action) source
let iteri action source = Internal.iter (CountableAction action) source
let iterAsync action source = Internal.iter (AsyncSimpleAction action) source
diff --git a/src/FSharpy.TaskSeq/TaskSeq.fsi b/src/FSharpy.TaskSeq/TaskSeq.fsi
index 50415e2c..7463d74a 100644
--- a/src/FSharpy.TaskSeq/TaskSeq.fsi
+++ b/src/FSharpy.TaskSeq/TaskSeq.fsi
@@ -87,6 +87,25 @@ module TaskSeq =
/// Create a taskSeq of an array of async.
val ofAsyncArray: source: Async<'T> array -> taskSeq<'T>
+ ///
+ /// Boxes as type each item in the sequence asynchyronously.
+ ///
+ val box: source: taskSeq<'T> -> taskSeq
+
+ ///
+ /// Unboxes to the target type each item in the sequence asynchyronously.
+ /// The target type must be a or a built-in value type.
+ ///
+ /// Thrown when the function is unable to cast an item to the target type.
+ val unbox<'U when 'U: struct> : source: taskSeq -> taskSeq<'U>
+
+ ///
+ /// Casts each item in the untyped sequence asynchyronously. If your types are boxed struct types
+ /// it is recommended to use instead.
+ ///
+ /// Thrown when the function is unable to cast an item to the target type.
+ val cast: source: taskSeq -> taskSeq<'T>
+
/// Iterates over the taskSeq applying the action function to each item. This function is non-blocking
/// exhausts the sequence as soon as the task is evaluated.
val iter: action: ('T -> unit) -> source: taskSeq<'T> -> Task
diff --git a/src/FSharpy.TaskSeq/Utils.fs b/src/FSharpy.TaskSeq/Utils.fs
index 12f2edcb..cefef0f1 100644
--- a/src/FSharpy.TaskSeq/Utils.fs
+++ b/src/FSharpy.TaskSeq/Utils.fs
@@ -48,6 +48,9 @@ module Task =
/// Bind a Task<'T>
let inline bind binder (task: Task<'T>) : Task<'U> = TaskBuilder.task { return! binder task }
+ /// Create a task from a value
+ let inline fromResult (value: 'U) : Task<'U> = TaskBuilder.task { return value }
+
module Async =
/// Convert an Task<'T> into an Async<'T>
let inline ofTask (task: Task<'T>) = Async.AwaitTask task