Applicative Computation Expressions - 2
In the last post we used
BindReturn
and MergeSources
to implement the applicative. The advantage of
implementing them is that it will work for any number of and!
.
When using let!
with a single and!
, it makes one call to MergeSources
to pair
arguments and one call to BindReturn
to pass values to the function.
When using let!
with two and!
, it makes two calls to MergeSources
to tuple
arguments and one call to BindReturn
to pass values to the function.
This can be expensive, so you can provide specific implementations for given numbers of parameters to reduce the number of calls.
For two arguments, you can implement Bind2Return
:
type AsyncBuilder with
member this.Bind2Return(x: 'a Async,y: 'b Async,f: 'a * 'b -> 'c) : 'c Async =
this.Bind(Async.StartChild x, fun xa ->
this.Bind(Async.StartChild y, fun ya ->
this.Bind(xa, fun xv ->
this.Bind(ya, fun yv -> this.Return (f(xv, yv)))
)
)
)
its signature is:
M<'a> * M<'b> * ('a * 'b -> 'c) -> M<'c>
Notice that it is similar to map2
signature.
We can check that it's working as expected:
async {
let! parisCoords =
async {
let! paris = ZipCode.AsyncLoad "http://api.zippopotam.us/fr/75020"
return coord paris }
and! londonCoords =
async {
let! london = ZipCode.AsyncLoad "http://api.zippopotam.us/GB/EC1"
return coord london}
return dist parisCoords londonCoords
}
|> Async.RunSynchronously
To handle a let! and! and!
case you have to provide a Bind3Return
member that
takes 3 values and a function.
You can also provide a Bind4Return
, Bind5Return
... and from my tests there is no limit
in the number of arguments you can test. F# would happily call a Bind265Return
if it had to.
Those additional functions are occasions to optimize the implementation by reducing the number of intermediate allocations and function calls.
Obviously, it's advised to provide only a few special cases and fall back to BindReturn
and MergeSources
for the rest.
Other options for implementation is to provide MergeSources3
, MergeSources4
to
reduce the number of intermediate tuples. The signature will be for MergeSources3
:
M<'a> * M<'b> * M<'c> -> M<'a * 'b * 'c>
It's also possible to define Bind2
, Bind3
with the following signatures:
M<'a> * M<'b> * ('a * 'b -> M<'c>) -> M<'c>
This looks like the Bind2Return
except that the function already returns a wrapped value.
You can find the complete documentation in the RFC
namespace FSharp
--------------------
namespace Microsoft.FSharp
namespace FSharp.Data
--------------------
namespace Microsoft.FSharp.Data
<summary>Typed representation of a JSON document.</summary> <param name='Sample'>Location of a JSON sample file or a string containing a sample JSON document.</param> <param name='SampleIsList'>If true, sample should be a list of individual samples for the inference.</param> <param name='RootName'>The name to be used to the root type. Defaults to `Root`.</param> <param name='Culture'>The culture used for parsing numbers and dates. Defaults to the invariant culture.</param> <param name='Encoding'>The encoding used to read the sample. You can specify either the character set name or the codepage number. Defaults to UTF8 for files, and to ISO-8859-1 the for HTTP requests, unless `charset` is specified in the `Content-Type` response header.</param> <param name='ResolutionFolder'>A directory that is used when resolving relative file references (at design time and in hosted execution).</param> <param name='EmbeddedResource'>When specified, the type provider first attempts to load the sample from the specified resource (e.g. 'MyCompany.MyAssembly, resource_name.json'). This is useful when exposing types generated by the type provider.</param> <param name='InferTypesFromValues'>If true, turns on additional type inference from values. (e.g. type inference infers string values such as "123" as ints and values constrained to 0 and 1 as booleans.)</param>
val decimal: value: 'T -> decimal (requires member op_Explicit)
<summary>Converts the argument to System.Decimal using a direct conversion for all primitive numeric types. For strings, the input is converted using <c>UInt64.Parse()</c> with InvariantCulture settings. Otherwise the operation requires an appropriate static conversion method on the input type.</summary>
<param name="value">The input value.</param>
<returns>The converted decimal.</returns>
--------------------
[<Struct>] type decimal = Decimal
<summary>An abbreviation for the CLI type <see cref="T:System.Decimal" />.</summary>
<category>Basic Types</category>
--------------------
type decimal<'Measure> = decimal
<summary>The type of decimal numbers, annotated with a unit of measure. The unit of measure is erased in compiled code and when values of this type are analyzed using reflection. The type is representationally equivalent to <see cref="T:System.Decimal" />.</summary>
<category>Basic Types with Units of Measure</category>
val float: value: 'T -> float (requires member op_Explicit)
<summary>Converts the argument to 64-bit float. This is a direct conversion for all primitive numeric types. For strings, the input is converted using <c>Double.Parse()</c> with InvariantCulture settings. Otherwise the operation requires an appropriate static conversion method on the input type.</summary>
<param name="value">The input value.</param>
<returns>The converted float</returns>
--------------------
[<Struct>] type float = Double
<summary>An abbreviation for the CLI type <see cref="T:System.Double" />.</summary>
<category>Basic Types</category>
--------------------
type float<'Measure> = float
<summary>The type of double-precision floating point numbers, annotated with a unit of measure. The unit of measure is erased in compiled code and when values of this type are analyzed using reflection. The type is representationally equivalent to <see cref="T:System.Double" />.</summary>
<category index="6">Basic Types with Units of Measure</category>
<summary>Cosine of the given number</summary>
<param name="value">The input value.</param>
<returns>The cosine of the input.</returns>
val double: value: 'T -> double (requires member op_Explicit)
<summary>Converts the argument to 64-bit float.</summary>
<remarks>This is a direct conversion for all primitive numeric types. For strings, the input is converted using <c>Double.Parse()</c> with InvariantCulture settings. Otherwise the operation requires and invokes a <c>ToDouble</c> method on the input type.</remarks>
--------------------
[<Struct>] type double = Double
<summary>An abbreviation for the CLI type <see cref="T:System.Double" />. Identical to <see cref="T:Microsoft.FSharp.Core.float" />.</summary>
<category>Basic Types</category>
--------------------
type double<'Measure> = float<'Measure>
<summary>The type of double-precision floating point numbers, annotated with a unit of measure. The unit of measure is erased in compiled code and when values of this type are analyzed using reflection. The type is representationally equivalent to <see cref="T:System.Double" />.</summary>
<category index="6">Basic Types with Units of Measure</category>
<summary>Provides constants and static methods for trigonometric, logarithmic, and other common mathematical functions.</summary>
<summary>Square root of the given number</summary>
<param name="value">The input value.</param>
<returns>The square root of the input.</returns>
Gets latitude/longitude for a returned zip info
<summary>The type of the <c>async</c> operator, used to build workflows for asynchronous computations.</summary>
<category index="1">Async Programming</category>
type Async = static member AsBeginEnd: computation: ('Arg -> Async<'T>) -> ('Arg * AsyncCallback * obj -> IAsyncResult) * (IAsyncResult -> 'T) * (IAsyncResult -> unit) static member AwaitEvent: event: IEvent<'Del,'T> * ?cancelAction: (unit -> unit) -> Async<'T> (requires delegate and 'Del :> Delegate) static member AwaitIAsyncResult: iar: IAsyncResult * ?millisecondsTimeout: int -> Async<bool> static member AwaitTask: task: Task<'T> -> Async<'T> + 1 overload static member AwaitWaitHandle: waitHandle: WaitHandle * ?millisecondsTimeout: int -> Async<bool> static member CancelDefaultToken: unit -> unit static member Catch: computation: Async<'T> -> Async<Choice<'T,exn>> static member Choice: computations: seq<Async<'T option>> -> Async<'T option> static member FromBeginEnd: beginAction: (AsyncCallback * obj -> IAsyncResult) * endAction: (IAsyncResult -> 'T) * ?cancelAction: (unit -> unit) -> Async<'T> + 3 overloads static member FromContinuations: callback: (('T -> unit) * (exn -> unit) * (OperationCanceledException -> unit) -> unit) -> Async<'T> ...
<summary>Holds static members for creating and manipulating asynchronous computations.</summary>
<remarks> See also <a href="https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/asynchronous-workflows">F# Language Guide - Async Workflows</a>. </remarks>
<category index="1">Async Programming</category>
--------------------
type Async<'T>
<summary> An asynchronous computation, which, when run, will eventually produce a value of type T, or else raises an exception. </summary>
<remarks> This type has no members. Asynchronous computations are normally specified either by using an async expression or the static methods in the <see cref="T:Microsoft.FSharp.Control.Async" /> type. See also <a href="https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/asynchronous-workflows">F# Language Guide - Async Workflows</a>. </remarks>
<namespacedoc><summary> Library functionality for asynchronous programming, events and agents. See also <a href="https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/asynchronous-workflows">Asynchronous Programming</a>, <a href="https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/members/events">Events</a> and <a href="https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/lazy-expressions">Lazy Expressions</a> in the F# Language Guide. </summary></namespacedoc>
<category index="1">Async Programming</category>
<summary>Builds an asynchronous workflow using computation expression syntax.</summary>
Loads JSON from the specified uri