f# - 泛型函数参数的类型推断

标签 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>带有神秘的警告(“...导致代码比指示的更不通用...”)。

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

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

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

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

(注意:我的实际用例不使用异步,而是使用另一个类似的计算表达式;我使用异步用于说明目的)。

最佳答案

最简单的选项是为第二个通用参数提供“请亲爱的编译器为我解决这个问题”符号(也称为下划线):

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

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

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 等一起使用时才求值。因此,您可以不使用 lambda 表达式:

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

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

关于f# - 泛型函数参数的类型推断,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38252388/

相关文章:

f# - 为什么使用基本关键字会导致此错误?

wpf - F# WPF : Handling click events in ListBox

在 F# 中触发时未调用 C# 事件处理程序

f# - 模式匹配中的不一致行为

f# - 如何实现不定式数据结构以在 F# 中按需获取项目

parsing - 为什么我的递归 FParsec 解析器在解析嵌套数组时会抛出异常?

c# - 我如何在 C# 中获得特定运算符的函数?

data-structures - 如何在函数式编程中实现内存有效的集合的无损操作?

data-structures - 实现不可变的、可增长的向量

f# - %A 在 F# 中是什么意思?