Skip to content
Open
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
5 changes: 5 additions & 0 deletions docs/release-notes/.FSharp.Core/11.0.100.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,8 @@
* Fix `Array.exists2` documentation examples to use equal-length arrays; the previous examples would throw `ArgumentException` at runtime instead of returning the documented `false`/`true` values. ([PR #19672](https://github.com/dotnet/fsharp/pull/19672))
* Move `Async.StartChild` to the "Starting Async Computations" docs category alongside `Async.StartChildAsTask`. ([Issue #19667](https://github.com/dotnet/fsharp/issues/19667))
* Add `InlineIfLambda` to `Array.init` ([PR #19869](https://github.com/dotnet/fsharp/pull/19869))

### Added

* Added modules for `Async`, `Task` and `ValueTask` with consistent `result`, `map`, `bind`, `ignore`, `catchWith`, `catch`, and `empty` functions ([LanguageSuggestion #1466](https://github.com/fsharp/fslang-suggestions/issues/1466), [PR #19844](https://github.com/dotnet/fsharp/pull/19844))
* Added conversion functions `Task.ofValueTask` and `ValueTask.ofTask`. ([LanguageSuggestion #1466](https://github.com/fsharp/fslang-suggestions/issues/1466), [PR #19844](https://github.com/dotnet/fsharp/pull/19844))
42 changes: 42 additions & 0 deletions src/FSharp.Core/async.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2355,3 +2355,45 @@ module WebExtensions =
start = (fun userToken -> this.DownloadFileAsync(address, fileName, userToken)),
result = (fun _ -> ())
)

[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
Comment thread
bartelink marked this conversation as resolved.
module Async =

[<CompiledName("Result")>]
let inline result (value: 'T) : Async<'T> =
async.Return value

[<CompiledName("Map")>]
let inline map ([<InlineIfLambda>] mapping: 'T -> 'U) (computation: Async<'T>) : Async<'U> =
async.Bind(computation, mapping >> async.Return)

[<CompiledName("Bind")>]
let inline bind ([<InlineIfLambda>] binder: 'T -> Async<'U>) (computation: Async<'T>) : Async<'U> =
async.Bind(computation, binder)

[<CompiledName("Ignore")>]
[<RequiresExplicitTypeArguments>]
let inline ignore<'T> (computation: Async<'T>) : Async<unit> =
Async.Ignore computation

[<CompiledName("CatchWith")>]
let catchWith (handler: exn -> 'T) (computation: Async<'T>) : Async<'T> =
async {
try
return! computation
with e ->
return handler e
}

[<CompiledName("Catch")>]
let catch (computation: Async<'T>) : Async<Result<'T, exn>> =
async {
try
let! v = computation
return Result.Ok v
with e ->
return Result.Error e
}

[<CompiledName("Empty")>]
let empty: Async<unit> = async.Zero()
121 changes: 120 additions & 1 deletion src/FSharp.Core/async.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -970,7 +970,7 @@ namespace Microsoft.FSharp.Control
/// use file = System.IO.File.OpenRead(filename)
/// printfn "Reading from file %s." filename
/// // Throw away the data being read.
/// do! file.AsyncRead(numBytes) |> Async.Ignore
/// do! file.AsyncRead(numBytes) |> Async.ignore&lt;byte[]&gt;
/// }
/// readFile "example.txt" 42 |> Async.Start
/// </code>
Expand Down Expand Up @@ -1548,3 +1548,122 @@ namespace Microsoft.FSharp.Control
module internal AsyncBuilderImpl =
val async : AsyncBuilder

/// <summary>Contains camelCase module-level functions for <see cref="T:Microsoft.FSharp.Control.FSharpAsync`1"/> computations.</summary>
///
/// <category index="1">Async Programming</category>
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module Async =

/// <summary>Creates an asynchronous computation that returns the given value.</summary>
///
/// <param name="value">The value to return.</param>
///
/// <returns>An asynchronous computation that returns <c>value</c> when executed.</returns>
///
/// <example id="async-result-1">
/// <code lang="fsharp">
/// let computation = Async.result 42
/// computation |> Async.RunSynchronously // evaluates to 42
/// </code>
/// </example>
[<CompiledName("Result")>]
val inline result: value: 'T -> Async<'T>

/// <summary>Creates an asynchronous computation that applies the mapping function to the result of the given computation.</summary>
///
/// <param name="mapping">The function to apply to the result.</param>
/// <param name="computation">The input computation.</param>
///
/// <returns>An asynchronous computation that applies <c>mapping</c> to the result of <c>computation</c>.</returns>
///
/// <example id="async-map-1">
/// <code lang="fsharp">
/// let computation = Async.result 21 |> Async.map (fun x -> x * 2)
/// computation |> Async.RunSynchronously // evaluates to 42
/// </code>
/// </example>
[<CompiledName("Map")>]
val inline map: mapping: ('T -> 'U) -> computation: Async<'T> -> Async<'U>

/// <summary>Creates an asynchronous computation that passes the result of the given computation to the binder function.</summary>
///
/// <param name="binder">A function that takes the result of the computation and returns a new asynchronous computation.</param>
/// <param name="computation">The input computation.</param>
///
/// <returns>An asynchronous computation that performs a monadic bind on the result of <c>computation</c>.</returns>
///
/// <example id="async-bind-1">
/// <code lang="fsharp">
/// let computation = Async.result 21 |> Async.bind (fun x -> Async.result (x * 2))
/// computation |> Async.RunSynchronously // evaluates to 42
/// </code>
/// </example>
[<CompiledName("Bind")>]
val inline bind: binder: ('T -> Async<'U>) -> computation: Async<'T> -> Async<'U>

/// <summary>Creates an asynchronous computation that runs the given computation and ignores its result.</summary>
///
/// <param name="computation">The input computation.</param>
///
/// <returns>A computation that is equivalent to the input computation, but disregards the result.</returns>
///
/// <example id="async-ignore-1">
/// <code lang="fsharp">
/// let readFile filename numBytes =
/// async {
/// use file = System.IO.File.OpenRead(filename)
/// do! file.AsyncRead(numBytes) |> Async.ignore&lt;byte[]&gt;
/// }
/// </code>
/// </example>
[<CompiledName("Ignore")>]
[<RequiresExplicitTypeArguments>]
val inline ignore<'T> : computation: Async<'T> -> Async<unit>

/// <summary>Creates an asynchronous computation that runs the given computation.
/// If it raises an exception, the handler function is called with the exception and its result is returned.</summary>
///
/// <param name="handler">A function to handle exceptions, returning a recovery value.</param>
/// <param name="computation">The input computation.</param>
///
/// <returns>An asynchronous computation that returns the result of <c>computation</c>, or the result of <c>handler</c> if an exception is raised.</returns>
///
/// <example id="async-catchwith-1">
/// <code lang="fsharp">
/// let safeDiv x y =
/// async { return x / y }
/// |> Async.catchWith (fun _ -> 0)
/// safeDiv 10 0 |> Async.RunSynchronously // evaluates to 0
/// </code>
/// </example>
[<CompiledName("CatchWith")>]
val catchWith: handler: (exn -> 'T) -> computation: Async<'T> -> Async<'T>

/// <summary>Creates an asynchronous computation that runs the given computation and returns its result as <c>Ok</c>,
/// or returns <c>Error</c> with the exception if one is raised.</summary>
///
/// <param name="computation">The input computation.</param>
///
/// <returns>An asynchronous computation that returns <c>Ok</c> of the result or <c>Error</c> of the exception.</returns>
///
/// <example id="async-catch-1">
/// <code lang="fsharp">
/// let safeDiv x y =
/// async { return x / y } |> Async.catch
/// safeDiv 10 2 |> Async.RunSynchronously // evaluates to Ok 5
/// safeDiv 10 0 |> Async.RunSynchronously // evaluates to Error (DivideByZeroException ...)
/// </code>
/// </example>
[<CompiledName("Catch")>]
val catch: computation: Async<'T> -> Async<Result<'T, exn>>

/// <summary>An asynchronous computation that returns <c>unit</c>. This is equivalent to <c>async.Zero()</c>.</summary>
///
/// <example id="async-empty-1">
/// <code lang="fsharp">
/// Async.empty |> Async.RunSynchronously // evaluates to ()
/// </code>
/// </example>
[<CompiledName("Empty")>]
val empty: Async<unit>

151 changes: 151 additions & 0 deletions src/FSharp.Core/tasks.fs
Original file line number Diff line number Diff line change
Expand Up @@ -716,3 +716,154 @@ module LowPlusPriority =
this.Bind(computation, fun (result2: ^TResult2) -> this.Return struct (result1, result2))
)
)

namespace Microsoft.FSharp.Control

open System.Threading.Tasks
open Microsoft.FSharp.Core
open TaskBuilder
open Microsoft.FSharp.Control.TaskBuilderExtensions
open Microsoft.FSharp.Control.TaskBuilderExtensions.LowPriority
open Microsoft.FSharp.Control.TaskBuilderExtensions.HighPriority

[<RequireQualifiedAccess>]
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module Task =

[<CompiledName("Result")>]
let inline result (value: 'T) : Task<'T> =
Task.FromResult value

[<CompiledName("Empty")>]
let empty: Task<unit> = result ()

[<CompiledName("Bind")>]
let inline bind ([<InlineIfLambda>] binder: 'T -> Task<'U>) (task: Task<'T>) : Task<'U> =
if task.Status = TaskStatus.RanToCompletion then
try
binder task.Result
with e ->
Task.FromException<'U>(e)
else
TaskBuilder.task {
let! v = task
return! binder v
}

[<CompiledName("Map")>]
let inline map ([<InlineIfLambda>] mapping: 'T -> 'U) (task: Task<'T>) : Task<'U> =
if task.Status = TaskStatus.RanToCompletion then
try
mapping task.Result |> result
with e ->
Task.FromException<'U>(e)
else
TaskBuilder.task {
let! v = task
return mapping v
}

[<CompiledName("Ignore")>]
[<RequiresExplicitTypeArguments>]
let inline ignore<'T> (task: Task<'T>) : Task<unit> =
Comment thread
bartelink marked this conversation as resolved.
if task.Status = TaskStatus.RanToCompletion then
empty
else
map ignore task

[<CompiledName("CatchWith")>]
let inline catchWith ([<InlineIfLambda>] handler: exn -> 'T) (task: Task<'T>) : Task<'T> =
Comment thread
bartelink marked this conversation as resolved.
if task.Status = TaskStatus.RanToCompletion then
task
else
TaskBuilder.task {
try
return! task
with
| :? System.OperationCanceledException as e -> return! raise e
| e -> return handler e
}

[<CompiledName("Catch")>]
Comment thread
bartelink marked this conversation as resolved.
let catch (task: Task<'T>) : Task<Result<'T, exn>> =
Comment thread
bartelink marked this conversation as resolved.
task |> map Ok |> catchWith Error

#if NETSTANDARD2_1
[<CompiledName("OfValueTask")>]
let inline ofValueTask (valueTask: ValueTask<'T>) : Task<'T> =
valueTask.AsTask()
#endif

#if NETSTANDARD2_1
[<RequireQualifiedAccess>]
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module ValueTask =

[<CompiledName("Result")>]
let inline result (value: 'T) : ValueTask<'T> =
ValueTask<'T>(value)

[<CompiledName("Empty")>]
let empty: ValueTask<unit> = result ()

[<CompiledName("OfTask")>]
let inline ofTask (task: Task<'T>) : ValueTask<'T> =
ValueTask<'T>(task)

[<CompiledName("Bind")>]
let inline bind ([<InlineIfLambda>] binder: 'T -> ValueTask<'U>) (task: ValueTask<'T>) : ValueTask<'U> =
if task.IsCompletedSuccessfully then
try
binder task.Result
with e ->
Task.FromException<'U>(e) |> ofTask
else
let t: Task<'U> =
TaskBuilder.task {
let! v = task
return! binder v
}

ValueTask<'U>(t)

[<CompiledName("Map")>]
let inline map ([<InlineIfLambda>] mapping: 'T -> 'U) (task: ValueTask<'T>) : ValueTask<'U> =
if task.IsCompletedSuccessfully then
try
mapping task.Result |> result
with e ->
Task.FromException<'U>(e) |> ofTask
else
let t: Task<'U> =
TaskBuilder.task {
let! v = task
return mapping v
}

ValueTask<'U>(t)

[<CompiledName("Ignore")>]
[<RequiresExplicitTypeArguments>]
let inline ignore<'T> (task: ValueTask<'T>) : ValueTask<unit> =
Comment thread
bartelink marked this conversation as resolved.
map ignore task

[<CompiledName("CatchWith")>]
let inline catchWith ([<InlineIfLambda>] handler: exn -> 'T) (task: ValueTask<'T>) : ValueTask<'T> =
if task.IsCompletedSuccessfully then
task
else
let t: Task<'T> =
TaskBuilder.task {
try
return! task
with
| :? System.OperationCanceledException as e -> return! raise e
| e -> return handler e
}

ValueTask<'T>(t)

[<CompiledName("Catch")>]
let catch (task: ValueTask<'T>) : ValueTask<Result<'T, exn>> =
task |> map Ok |> catchWith Error
#endif
Loading
Loading