【问题标题】:F# compiler inferring concrete types from first use of generic functions when curryingF# 编译器在柯里化时从第一次使用泛型函数推断具体类型
【发布时间】:2015-01-26 08:39:55
【问题描述】:

我在类型推断和柯里化方面遇到问题。

我有一个这样的辅助方法:

requestToGroup :
    group:'T array ->
      operation:('T -> System.Threading.Tasks.Task<'B>) ->
        predicate:('B -> bool) -> timeout:int -> Async<int>

基本上,这种方法允许我在多个服务中并行启动相同的操作,当所有服务都完成或超时到期时,返回有多少成功使用谓词评估它们。

考虑这些简单的定义:

  type RequestA(name:string) =
    member val Name=name with get,set

  type ResultA(has:bool)=
    member x.Has()=has

  type RequestB(id:int) =
    member val Id=id with get,set

  type ResultB(fits:bool)=
    member x.Fits()=fits

  type IService =
    abstract member Has: RequestA -> Task<ResultA>
    abstract member Fits: RequestB -> Task<ResultB>

我可以像这样使用这个助手:

  type MyClass<'T>(services:IService array) =

    member x.AllHas(value:string) =
      let positive = Async.RunSynchronously <| requestToGroup services (fun(s)->s.Has(RequestA(value))) (fun(r)->r.Has()) 1000
      positive=services.Length
    member x.AllFits(value:int, services:IService array) =
      let positive = Async.RunSynchronously <| requestToGroup services (fun(s)->s.Fits(RequestB(value))) (fun(r)->r.Fits()) 1000
      positive=services.Length

这一切都很好。然后我决定要咖喱 requestToGroup 函数,方法是:

  type MyClass<'T>(services:IService array) =

    let groupOp = requestToGroup services

    member x.AllHas(value:string) =
      let positive = Async.RunSynchronously <| groupOp (fun(s)->s.Has(RequestA(value))) (fun(r)->r.Has()) 1000
      positive=services.Length
    member x.AllFits(value:int, services:IService array) =
      let positive = Async.RunSynchronously <| groupOp (fun(s)->s.Fits(RequestB(value))) (fun(r)->r.Fits()) 1000
      positive=services.Length

但是现在,groupOp 推断出具体类型而不是泛型,并且在 s.Fits(RequestB(value)) 处编译失败,说这是期待 Task&lt;RequestA&gt; 但我提供的是 Task&lt;RequestB&gt;

如何防止编译器推断具体类型?

奖励:如何使 MyClass 方法中的代码看起来更好、更清晰?

【问题讨论】:

    标签: generics f# type-inference currying


    【解决方案1】:

    自动泛化不适用于值绑定,就像它应用于函数绑定一样。这意味着如果绑定不是语法函数,即没有参数,它可能会导致问题。试试这个:

    let groupOp a = requestToGroup services a
    

    (在静态分辨率的情况下,可能还需要inline,但这似乎与这里无关。)


    编辑:代码建议(可能完全不合时宜)

    在重构时,我只是发布一些疯狂的想法,并留给您它们是否有用。如果它不适用,请不要介意。将有一个小的提案和一个大的变化。

    作为一个轻量级的重构,我建议让请求和结果类型不可变。它们可以这样定义:

    type RequestA = RequestA of name : string
    type ResultA = ResultA of has : bool
    
    type RequestB = RequestB of ident : int
    type ResultB = ResultB of fits : bool
    

    这是使用 F# 3.1 中引入的命名 DU 字段。旧版本必须省略名称。 使用这些,我可以在MyClass 中编写一个通用的checkAll 函数。如果你想坚持使用旧类型,那不会改变函数的形状。

    type MyClass<'T>(services:IService array) =
        let checkAll operation predicate =
            let op = requestToGroup services operation predicate 1000
            Async.RunSynchronously op = Array.length services
    
        member x.AllHas value =
            checkAll (fun s -> s.Has(RequestA(value))) (fun (ResultA a) -> a)
        member x.AllFits value =
            checkAll (fun s -> s.Fits(RequestB(value))) (fun (ResultB b) -> b)
    

    (我假设问题中AllFits 方法的参数services 是重构工件;它会影响同名的私有值。)


    更重的重构我不知道这是否过火,但可以更改签名并完全放弃请求/响应类型。

    requestToGroup 正在做两件不一定相关的事情:在注意超时的同时并行执行任务和使用谓词计算结果。让第一部分成为一个单独的函数怎么样:

    let tryRunAll (group : 'T array) (getJob : 'T -> Async<'B>) (timeout : int) =
        // ... result typed Async<'B option []>
    

    它将返回一个结果数组,其中None 表示超时。如果您想直接处理结果,则此函数本身可能很有用。

    无论如何,IService 可以与异步一起工作。可以使用Async.StartAsTask 将它们转换为任务。

    type IService =
        abstract member HasName: string -> Async<bool>
        abstract member FitsId: int -> Async<bool>
    

    那么,MyClass 的实现将如下所示:

    type MyClass<'T> (services : IService array) =
        let checkAll getter =
            Async.RunSynchronously (tryRunAll services getter 1000)
            |> Array.forall ((=) (Some true))
    
        member x.AllHas value = checkAll (fun s -> s.HasName value)
        member x.AllFits value = checkAll (fun s -> s.FitsId value)
    

    如果还需要requestToGroup,可以这样实现:

    let requestToGroup group operation predicate timeout = async {
        let! res = tryRunAll group operation timeout
        return res |> Array.choose id |> Array.filter predicate |> Array.length }
    

    我想象中的编码尝试到此结束。 不保证其中任何内容是正常的、会起作用或适用的。但希望它有助于获得想法。

    【讨论】:

    • 哇,谢谢。我无法进行一些更改,因为我需要使用 C# 中的库,但我有好主意
    • @vtortola 太好了,很高兴它有帮助。 糟糕,我刚刚注意到,checkAll 没有检查布尔值,并且最后一个 sn-p 的签名错误。修复它们,以防万一。
    猜你喜欢
    • 1970-01-01
    • 2020-08-20
    • 1970-01-01
    • 2021-06-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-03
    • 2019-12-06
    相关资源
    最近更新 更多