异步链接操作的类型不匹配错误

时间:2018-03-31 09:58:24

标签: f#

之前我的question有一个非常紧凑和全面的答案。

我让它适用于我的自定义类型但现在由于某种原因我不得不将其更改为字符串类型,这现在导致类型不匹配错误。

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

铁路导向功能

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"}
     }

测试

AsyncResult.bind

的类型不匹配错误
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)

错误详情

  

EntityTests.fs(101,25):[FS0001]类型不匹配。期待'(string -> string -> Async<Result<string,Error>>) -> 'a',但'Async<Result<'b,'c>> -> Async<Result<'d,'c>>' 'string -> string -> Async<Result<string,Error>>'类型与'Async<Result<'a,'b>>'类型不匹配。

     

EntityTests.fs(101,25):[FS0001]类型不匹配。期待'(string -> string -> Async<Result<string,Error>>) -> 'a',但'Async<Result<string,'b>> -> Async<Result<string,'b>>' 'string -> string -> Async<Result<string,Error>>'类型与'Async<Result<string,'a>>'类型不匹配。

修改

咖喱或部分申请

在上面的例子中,是否存在curried函数的问题?例如,如果create function具有此签名。

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

然后使用curried函数构建链

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

编辑2 以下情况有问题吗?

签名

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

测试

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

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

2 个答案:

答案 0 :(得分:1)

我怀疑错误源于缩进的微小变化,因为向FSharp程序添加单个空格会改变其含义,FSharp编译器会快速报告幻像错误,因为它会以不同的方式解释输入。我只是粘贴它并添加了虚假类并删除了一些空格,现在它工作得很好。

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

答案 1 :(得分:1)

更新:在答案的前面,甚至,因为你的编辑2改变了所有内容

在您的编辑2中,您终于展示了实际代码,问题非常简单:您误解了这些类型在咖喱F#函数中的工作方式。

当您的create函数看起来像let create (json: string) = ...时,它是一个参数的函数。它需要一个字符串,并返回一个结果类型(在本例中为Async<Result<string, Error>>)。所以函数签名是string -> Async<Result<string, Error>>

但是,您刚刚向我们展示的create功能完全是另一种类型。它需要四个参数(一个Token和三个字符串),而不是一个。这意味着它的签名是:

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

记住currying如何工作:多个参数的任何函数都可以被认为是一个参数的一系列函数,它们返回&#34; next&#34;该链中的功能。例如,let add3 a b c = a + b + c的类型为int -> int -> int -> int;这意味着add3 1会返回一个等同于let add2 b c = 1 + b + c的函数。等等。

现在,记住curry,看看你的功能类型。当您将单个令牌值传递给它时,就像在示例中那样(它被称为create token),您将获得类型函数:

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

这是一个函数,它接受一个字符串,它返回带有字符串的另一个函数,它返回 第三个函数字符串并返回Async<Result<whatever>>。现在将其与binder函数中bind参数的类型进行比较:

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

此处'astring'b'cErrorbind。因此,当通用string -> Async<Result<'b, 'c>>函数应用于您的特定情况时,它会查找类型为string -> string -> string -> Async<Result<string, Error>>的函数。但是你给它一个bind类型的函数。这两种功能类型不一样

这是造成类型错误的根本原因。您正在尝试应用函数,该函数返回一个返回函数的函数,该函数返回类型为X 的函数,并返回到需要apply设计模式) >返回类型X 的结果的函数。您需要的是名为apply的设计模式。我不得不很快离开所以我没有时间给你解释如何使用apply,但幸运的是Scott Wlaschin has already written a good one。它涵盖了很多,不只是&#34; apply&#34;,但您也可以在那里找到有关bind的详细信息。这就是问题的原因:当您需要使用apply时,您使用了AsyncResult.bind

原始答案如下:

我还不知道是什么导致了你的问题,但我怀疑。但首先,我想评论一下 let bind (binder : 'a -> Async<Result<'b, 'c>>) (asyncFun : Async<Result<'a, 'c>>) : Async<Result<'b, 'c>> = 的参数名称是错误的。这是你写的:

asyncFun

(我将第二个参数与第一个参数一致,因此它不会在Stack Overflow上滚动小的列大小,但如果类型正确则可以正确编译:因为这两个参数是垂直排列,F#会知道它们都属于同一个&#34; parent&#34;,在这种情况下是一个函数。)

看看你的第二个参数。您已将其命名为something -> somethingElse,但其类型说明中没有箭头。这不是一个功能,它是一个价值。函数看起来像asyncValue。您应该将其命名为asyncFun,而不是asyncFun。通过命名let chain = create >> AsyncResult.bind (fun (result: string) -> (async {return Ok "more results"})) ,您可以在以后混淆自己。

现在回答你提出的问题。我认为你的问题是这一行,你已经与F# "offside rule"发生了冲突:

>>

请注意let chain = create >> AsyncResult.bind (fun (result: string) -> (async {return Ok "more results"})) 运算符的位置,位于其第一个操作数的左侧。是的,在大多数情况下,F#语法似乎允许这样,但我怀疑如果您只是将该函数定义更改为以下内容,您的代码将起作用:

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

或者,更好的是因为it's good style to make the |> (and >>) operators line up with their first operand

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

如果你仔细看看Scott Wlaschin在https://fsharpforfunandprofit.com/posts/fsharp-syntax/中提出的规则,你会注意到他的例子中他显示了超越规则的例外情况&#34;他写了这些规则像这样:

>>

请注意函数定义中=字符仍然是<{1}}的。我不确切地知道F#规范关于功能定义和越位规则的组合的说法(Scott Wlaschin很棒,但他不是规范,所以他可能是错的,我不会&#39;现在有时间查看规范),但我已经看到它做了一些有趣的事情,当我在功能定义的部分功能定义与函数相同的情况下编写函数时,我并没有期待这些事情,其余的就在下一行。

例如,我曾经写过这样的东西,但是没有用:

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

但后来我把它改成了这个,确实有效:

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

我注意到在Snapshot的回答中,他让你的chain函数被定义在一行上,这对他有用。所以我怀疑这是你的问题。

经验法则:如果您的函数在同一行的=之后有任何内容,请将该函数全部放在一行上。如果你的函数是两行,那么在=之后什么都不放。 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