2016-07-07 172 views
2

比方说,我想延长的xUnit的Assert.Throws支持F#异步这样:泛型函数参数类型推断

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

的implentation是这样的:

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

有没有办法只提供异常的类型,但不是异步函数? (注意:我的实际用例不使用异步,而是使用另一个类似的计算表达式;为了便于说明,我使用了异步)。

回答

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#任务不同,F#异步值不会立即得到评估,而只能作为另一个异步的一部分或直接与Async.Start等人一起使用。因此,您可以不使用拉姆达表达式:

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

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