假设我想以这种方式扩展 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>
("...导致代码不如指示的通用...")。
let AsyncThrows<'TException, 'a when 'TException :> exn> (asyncTask:unit->Async<'a>)
这有效,但强制调用方显式提供异步函数的返回类型,例如
Assert.AsyncThrows<InvalidOperationException, string>(fun () -> async { return "" } )
有没有办法只需要提供异常的类型而不提供异步函数的类型?
(注意:我的实际用例不使用异步,而是使用另一个类似的计算表达式;我使用异步进行说明)。
最不复杂的选项是为第二个泛型参数提供"请亲爱的编译器为我解决这个问题"符号(又名下划线):
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# 任务不同,F# 异步值不会立即进行评估,而只会在用作另一个异步的一部分或直接与 Async.Start
et al 一起使用时进行评估。因此,您可以不使用 lambda 表达式:
...
let AsyncThrows<'e, 'a when 'e :> exn> (_ :ExnType<'e>) (a: Async<'a>) =
// Implementation here
let! r = a
...
// Usage:
AsyncThrows exnType<NotImplementedException> (async { return "" })