通用函数参数的类型推断

时间:2016-07-07 18:05:54

标签: f#

我想说我想以这种方式扩展xUnit的Assert.Throws以支持F#异步:

Assert.AsyncThrows<InvalidOperationException>(fun () -> async { return "" })

实施如下:

module Xunit.Assert

let AsyncThrows<'TException when 'TException :> exn> asyncFunc = async {

    let mutable actualException = None
    try
        let! r = asyncFunc()
        return ()
    with 
    | :? 'TException as e -> actualException <- Some e
    | _ -> ()      

    return Assert.Throws(
            (fun () -> 
                match actualException with
                | Some ex -> raise ex
                | None -> ()))        
}

asyncFunc的类型推断为unit -> Async<obj>。这对呼叫者来说是不必要的限制;它应该是unit -> Async<'a>。我尝试过以下方法:

let AsyncThrows<'TException when 'TException :> exn> (asyncTask:unit->Async<'a>)

这不起作用,仍然会以Async<obj>编译并带有神秘警告(&#34; ...导致代码不如指示的那样通用......&#34;)。< / p>

let AsyncThrows<'TException, 'a when 'TException :> exn> (asyncTask:unit->Async<'a>)

这样可行,但强制调用者明确提供异步函数的返回类型,例如

Assert.AsyncThrows<InvalidOperationException, string>(fun () -> async { return "" } )

有没有办法只提供异常类型而不是异步函数?

(注意:我的实际用例并不是使用异步,而是另一个类似的计算表达式;我使用async作为插图目的)。

1 个答案:

答案 0 :(得分:4)

最简单的选择是为第二个通用参数提供“请亲爱的编译器为我提供这个”标志(也就是下划线):

AsyncThrows<InvalidOperationException, _>( fun() -> async { return "" } )

另一种选择是通过返回接口来分阶段提供类型参数。这样,可以推断出第二个参数:

type IAsyncAssert<'e when 'e :> exn> =
  abstract member When<'a> : (unit -> Async<'a>) -> unit


let AsyncThrows<'e when 'e :> exn> () = 
    { new IAsyncAssert<'e> with
        override x.When<'a> (fn: unit -> Async<'a>) = 
          // Implementation goes here
    }

// Usage:
AsyncThrows<NotImplementedException>().When( fun() -> async { return "" } )

另一个(功能更强大的)选项是提供一个正确类型的“虚拟对象”来推断泛型参数:

type ExnType<'e when 'e :> exn> = | ExnType

let exnType<'e when 'e :> exn> : ExnType<'e> = ExnType 

let AsyncThrows<'e, 'a when 'e :> exn> (_ :ExnType<'e>) (fn: unit -> Async<'a>) = 
   // Implementation here

// Usage:
AsyncThrows exnType<NotImplementedException> ( fun() -> async { return "" } )

另外,这里有一个提示:与C#Tasks不同,F#异步值不会立即得到评估,但仅在作为另一个异步的一部分使用时或直接与Async.Start等人一起使用时。因此,您可以不使用lambda表达式:

...
let AsyncThrows<'e, 'a when 'e :> exn> (_ :ExnType<'e>) (a: Async<'a>) = 
   // Implementation here
   let! r = a
   ...

// Usage:
AsyncThrows exnType<NotImplementedException> (async { return "" })