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'> This parameter is deprecated. Please use InferenceMode instead. 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> <param name='PreferDictionaries'>If true, json records are interpreted as dictionaries when the names of all the fields are inferred (by type inference rules) into the same non-string primitive type.</param> <param name='InferenceMode'>Possible values: | NoInference -> Inference is disabled. All values are inferred as the most basic type permitted for the value (i.e. string or number or bool). | ValuesOnly -> Types of values are inferred from the Sample. Inline schema support is disabled. This is the default. | ValuesAndInlineSchemasHints -> Types of values are inferred from both values and inline schemas. Inline schemas are special string values that can define a type and/or unit of measure. Supported syntax: typeof<type> or typeof{type} or typeof<type<measure>> or typeof{type{measure}}. Valid measures are the default SI units, and valid types are <c>int</c>, <c>int64</c>, <c>bool</c>, <c>float</c>, <c>decimal</c>, <c>date</c>, <c>datetimeoffset</c>, <c>timespan</c>, <c>guid</c> and <c>string</c>. | ValuesAndInlineSchemasOverrides -> Same as ValuesAndInlineSchemasHints, but value inferred types are ignored when an inline schema is present. </param>
val decimal: value: 'T -> decimal (requires member op_Explicit)
--------------------
type decimal = Decimal
--------------------
type decimal<'Measure> = decimal
val float: value: 'T -> float (requires member op_Explicit)
--------------------
type float = Double
--------------------
type float<'Measure> = float
val double: value: 'T -> double (requires member op_Explicit)
--------------------
type double = Double
--------------------
type double<'Measure> = float<'Measure>
<summary>Provides constants and static methods for trigonometric, logarithmic, and other common mathematical functions.</summary>
Gets latitude/longitude for a returned zip info
type Async = static member AsBeginEnd: computation: ('Arg -> Async<'T>) -> ('Arg * AsyncCallback * objnull -> 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: Async<'T option> seq -> Async<'T option> static member FromBeginEnd: beginAction: (AsyncCallback * objnull -> IAsyncResult) * endAction: (IAsyncResult -> 'T) * ?cancelAction: (unit -> unit) -> Async<'T> + 3 overloads static member FromContinuations: callback: (('T -> unit) * (exn -> unit) * (OperationCanceledException -> unit) -> unit) -> Async<'T> ...
--------------------
type Async<'T>
Loads JSON from the specified uri