// thinkbeforecoding

Applicative Computation Expressions - 2

2020-10-08T08:18:10 / jeremie chassaing

In the last post we used BindReturnand 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 paramters to reduce the number of calls.

For two arguments, you can implement Bind2Return:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
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:

1: 
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:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
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 optimise 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:

1: 
M<'a> * M<'b> * M<'c> -> M<'a * 'b * 'c>

It's also possible to define Bind2, Bind3 with the following signatures:

1: 
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 System
Multiple items
namespace FSharp

--------------------
namespace Microsoft.FSharp
Multiple items
namespace FSharp.Data

--------------------
namespace Microsoft.FSharp.Data
type ZipCode = JsonProvider<...>
type JsonProvider


<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 dist : lata:decimal * longa:decimal -> latb:decimal * longb:decimal -> decimal
val lata : decimal
Multiple items
val decimal : value:'T -> decimal (requires member op_Explicit)

--------------------
type decimal = Decimal

--------------------
type decimal<'Measure> = decimal
val longa : decimal
val latb : decimal
val longb : decimal
val x : float
Multiple items
val float : value:'T -> float (requires member op_Explicit)

--------------------
type float = Double

--------------------
type float<'Measure> = float
val cos : value:'T -> 'T (requires member Cos)
Multiple items
val double : value:'T -> double (requires member op_Explicit)

--------------------
type double = Double
type Math =
  static val E : float
  static val PI : float
  static member Abs : value:float -> float + 6 overloads
  static member Acos : d:float -> float
  static member Acosh : d:float -> float
  static member Asin : d:float -> float
  static member Asinh : d:float -> float
  static member Atan : d:float -> float
  static member Atan2 : y:float * x:float -> float
  static member Atanh : d:float -> float
  ...
field Math.PI: float = 3.14159265359
val y : float
val z : float
val sqrt : value:'T -> 'U (requires member Sqrt)
val coord : zip:JsonProvider<...>.Root -> decimal * decimal


 Gets latitude/logitude for a returned zip info
val zip : JsonProvider<...>.Root
type Root =
  inherit IJsonDocument
  new : postCode: string * country: string * countryAbbreviation: string * places: Placis [] -> Root + 1 overload
  member Country : string
  member CountryAbbreviation : string
  member JsonValue : JsonValue
  member Places : Placis []
  member PostCode : string
property JsonProvider<...>.Root.Places: JsonProvider<...>.Placis [] with get
type AsyncBuilder =
  private new : unit -> AsyncBuilder
  member Bind : computation:Async<'T> * binder:('T -> Async<'U>) -> Async<'U>
  member Combine : computation1:Async<unit> * computation2:Async<'T> -> Async<'T>
  member Delay : generator:(unit -> Async<'T>) -> Async<'T>
  member For : sequence:seq<'T> * body:('T -> Async<unit>) -> Async<unit>
  member Return : value:'T -> Async<'T>
  member ReturnFrom : computation:Async<'T> -> Async<'T>
  member TryFinally : computation:Async<'T> * compensation:(unit -> unit) -> Async<'T>
  member TryWith : computation:Async<'T> * catchHandler:(exn -> Async<'T>) -> Async<'T>
  member Using : resource:'T * binder:('T -> Async<'U>) -> Async<'U> (requires 'T :> IDisposable)
  ...
val this : AsyncBuilder
val x : Async<'a>
Multiple items
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 -> Async<unit>
  static member AwaitTask : task:Task<'T> -> Async<'T>
  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>
  ...

--------------------
type Async<'T> =
val y : Async<'b>
val f : ('a * 'b -> 'c)
member AsyncBuilder.Bind : computation:Async<'T> * binder:('T -> Async<'U>) -> Async<'U>
static member Async.StartChild : computation:Async<'T> * ?millisecondsTimeout:int -> Async<Async<'T>>
val xa : Async<'a>
val ya : Async<'b>
val xv : 'a
val yv : 'b
member AsyncBuilder.Return : value:'T -> Async<'T>
val async : AsyncBuilder
val parisCoords : decimal * decimal
val paris : JsonProvider<...>.Root
JsonProvider<...>.AsyncLoad(uri: string) : Async<JsonProvider<...>.Root>


Loads JSON from the specified uri
val london : JsonProvider<...>.Root
static member Async.RunSynchronously : computation:Async<'T> * ?timeout:int * ?cancellationToken:Threading.CancellationToken -> 'T