Developer11
Developer11

Reputation: 781

type mismatch error for async chained operations

Previously had a very compact and comprehensive answer for my question.

I had it working for my custom type but now due to some reason I had to change it to string type which is now causing type mismatch errors.

module AsyncResult =
    let bind (binder : 'a -> Async<Result<'b, 'c>>) (asyncFun : Async<Result<'a, 'c>>) : Async<Result<'b, 'c>> =
        async {
            let! result = asyncFun
            match result with
            | Error e -> return Error e
            | Ok x -> return! binder x
        }

    let compose (f : 'a -> Async<Result<'b, 'e>>) (g : 'b -> Async<Result<'c, 'e>>) = fun x -> bind g (f x)
    let (>>=) a f = bind f a
    let (>=>) f g = compose f g

Railway Oriented functions

let create (json: string) : Async<Result<string, Error>> =
    let url = "http://api.example.com"
    let request = WebRequest.CreateHttp(Uri url)
    request.Method <- "GET"

     async {
         try
             // http call
             return Ok "result"
         with :? WebException as e -> 
             return Error {Code = 500; Message = "Internal Server Error"}
     }

test

type mismatch error for the AsyncResult.bind line

let chain = create
         >> AsyncResult.bind (fun (result: string) -> (async {return Ok "more results"}))


match chain "initial data" |> Async.RunSynchronously with
 | Ok data -> Assert.IsTrue(true)
 | Error error -> Assert.IsTrue(false)

Error details:

EntityTests.fs(101, 25): [FS0001] Type mismatch. Expecting a '(string -> string -> Async<Result<string,Error>>) -> 'a' but given a 'Async<Result<'b,'c>> -> Async<Result<'d,'c>>' The type 'string -> string -> Async<Result<string,Error>>' does not match the type 'Async<Result<'a,'b>>'.

EntityTests.fs(101, 25): [FS0001] Type mismatch. Expecting a '(string -> string -> Async<Result<string,Error>>) -> 'a' but given a 'Async<Result<string,'b>> -> Async<Result<string,'b>>' The type 'string -> string -> Async<Result<string,Error>>' does not match the type 'Async<Result<string,'a>>'.

Edit

Curried or partial application

In context of above example, is it the problem with curried functions? for instance if create function has this signature.

let create (token: string) (json: string) : Async<Result<string, Error>> =

and then later build chain with curried function

let chain = create "token" >> AsyncResult.bind (fun (result: string) -> (async {return Ok "more results"}))

Edit 2 Is there a problem with following case?

signature

let create (token: Token) (entityName: string) (entityType: string) (publicationId: string) : Async<Result<string, Error>> =

test

let chain = create token >> AsyncResult.bind ( fun (result: string) -> async {return Ok "more results"} )

match chain "test" "article" "pubid" |> Async.RunSynchronously with

Upvotes: 3

Views: 563

Answers (2)

rmunn
rmunn

Reputation: 36678

Update: At the front of the answer, even, since your edit 2 changes everything.

In your edit 2, you have finally revealed your actual code, and your problem is very simple: you're misunderstanding how the types work in a curried F# function.

When your create function looked like let create (json: string) = ..., it was a function of one parameter. It took a string, and returned a result type (in this case, Async<Result<string, Error>>). So the function signature was string -> Async<Result<string, Error>>.

But the create function you've just shown us is a different type entirely. It takes four parameters (one Token and three strings), not one. That means its signature is:

Token -> string -> string -> string -> Async<Result<string, Error>>

Remember how currying works: any function of multiple parameters can be thought of as a series of functions of one parameter, which return the "next" function in that chain. E.g., let add3 a b c = a + b + c is of type int -> int -> int -> int; this means that add3 1 returns a function that's equivalent to let add2 b c = 1 + b + c. And so on.

Now, keeping currying in mind, look at your function type. When you pass a single Token value to it as you do in your example (where it's called as create token, you get a function of type:

string -> string -> string -> Async<Result<string, Error>>

This is a function that takes a string, which returns another function that takes a string, which returns a third function which takes a string and returns an Async<Result<whatever>>. Now compare that to the type of the binder parameter in your bind function:

(binder : 'a -> Async<Result<'b, 'c>>)

Here, 'a is string, so is 'b, and 'c is Error. So when the generic bind function is applied to your specific case, it's looking for a function of type string -> Async<Result<'b, 'c>>. But you're giving it a function of type string -> string -> string -> Async<Result<string, Error>>. Those two function types are not the same!

That's the fundamental cause of your type error. You're trying to apply a function that returns a function that returns function that returns a result of type X to a design pattern (the bind design pattern) that expects a function that returns a result of type X. What you need is the design pattern called apply. I have to leave quite soon so I don't have time to write you an explanation of how to use apply, but fortunately Scott Wlaschin has already written a good one. It covers a lot, not just "apply", but you'll find the details about apply in there as well. And that's the cause of your problem: you used bind when you needed to use apply.

Original answer follows:

I don't yet know for a fact what's causing your problem, but I have a suspicion. But first, I want to comment that the parameter names for your AsyncResult.bind are wrong. Here's what you wrote:

    let bind (binder : 'a -> Async<Result<'b, 'c>>)
             (asyncFun : Async<Result<'a, 'c>>) : Async<Result<'b, 'c>> =

(I moved the second parameter in line with the first parameter so it wouldn't scroll on Stack Overflow's smallish column size, but that would compile correctly if the types were right: since the two parameters are lined up vertically, F# would know that they are both belonging to the same "parent", in this case a function.)

Look at your second parameter. You've named it asyncFun, but there's no arrow in its type description. That's not a function, it's a value. A function would look like something -> somethingElse. You should name it something like asyncValue, not asyncFun. By naming it asyncFun, you're setting yourself up for confusion later.

Now for the answer to the question you asked. I think your problem is this line, where you've fallen afoul of the F# "offside rule":

let chain = create
         >> AsyncResult.bind (fun (result: string) -> (async {return Ok "more results"}))

Note the position of the >> operator, which is to the left of its first operand. Yes, the F# syntax appears to allow that in most situations, but I suspect that if you simply change that function definition to the following, your code will work:

let chain =
    create
 >> AsyncResult.bind (fun (result: string) -> (async {return Ok "more results"}))

Or, better yet because it's good style to make the |> (and >>) operators line up with their first operand:

let chain =
    create
    >> AsyncResult.bind (fun (result: string) -> (async {return Ok "more results"}))

If you look carefully at the rules that Scott Wlaschin lays out in https://fsharpforfunandprofit.com/posts/fsharp-syntax/, you'll note that his examples where he shows exceptions to the "offside rule", he writes them like this:

let f g h =   g   // defines a new line at col 15
           >> h   // ">>" allowed to be outside the line

Note how the >> character is still to the right of the = in the function definition. I don't know exactly what the F# spec says about the combination of function definitions and the offside rule (Scott Wlaschin is great, but he's not the spec so he could be wrong, and I don't have time to look up the spec right now), but I've seen it do funny things that I didn't quite expect when I wrote functions with part of the function definition on the same line as the function, and the rest on the next line.

E.g., I once wrote something like this, which didn't work:

let f a = if a = 0 then
        printfn "Zero"
    else
        printfn "Non-zero"

But then I changed it to this, which did work:

let f a =
    if a = 0 then
        printfn "Zero"
    else
        printfn "Non-zero"

I notice that in Snapshot's answer, he made your chain function be defined on a single line, and that worked for him. So I suspect that that's your problem.

Rule of thumb: If your function has anything after the = on the same line, make the function all on one line. If your function is going to be two lines, put nothing after the =. E.g.:

let f a b = a + b  // This is fine
let g c d =
    c * d  // This is also fine
let h x y = x
          + y  // This is asking for trouble

Upvotes: 1

ZiggZagg
ZiggZagg

Reputation: 1427

I would suspect that the error stems from a minor change in indentation since adding a single space to an FSharp program changes its meaning, the FSharp compiler than quickly reports phantom errors because it interprets the input differently. I just pasted it in and added bogus classes and removed some spaces and now it is working just fine.

module AsyncResult =

    [<StructuralEquality; StructuralComparison>]
    type Result<'T,'TError> = 
    | Ok of ResultValue:'T
    | Error of ErrorValue:'TError

    let bind (binder : 'a -> Async<Result<'b, 'c>>) (asyncFun : Async<Result<'a, 'c>>) : Async<Result<'b, 'c>> =
        async {
            let! result = asyncFun
            match result with
            | Error e -> return Error e
            | Ok x -> return! binder x
        }

    let compose (f : 'a -> Async<Result<'b, 'e>>) (g : 'b -> Async<Result<'c, 'e>>) = fun x -> bind g (f x)
    let (>>=) a f = bind f a
    let (>=>) f g = compose f g

open AsyncResult
open System.Net

type Assert =
    static member IsTrue (conditional:bool) = System.Diagnostics.Debug.Assert(conditional)


type Error = {Code:int; Message:string}

[<EntryPoint>]
let main args =
    let create (json: string) : Async<Result<string, Error>> =
        let url = "http://api.example.com"
        let request = WebRequest.CreateHttp(Uri url)
        request.Method <- "GET"

        async {
             try
                 // http call
                 return Ok "result"
             with :? WebException as e -> 
                 return Error {Code = 500; Message = "Internal Server Error"}
        }

    let chain = create >> AsyncResult.bind (fun (result: string) -> (async {return Ok "more results"}))

    match chain "initial data" |> Async.RunSynchronously with
    | Ok data -> Assert.IsTrue(true)
    | Error error -> Assert.IsTrue(false)

    0

Upvotes: 1

Related Questions