首页 > 解决方案 > 如何绕过通用高阶函数 F# 的类型推断限制

问题描述

这个问题似乎很愚蠢,但我在网上找不到类似的东西。所以我来了。

我需要使用一个类似于集合的结构,该结构应该用作 Map 中的值。此集合具有通用类型,并且映射应包含任何类型的这些集合(int、boolean、string 等)。鉴于这不可能直接(因为泛型类型在 Map 定义中被限制为唯一值),我使用辅助 DU 来匹配所有情况,因为它们很少。我的问题是我想编写一个通用函数,它接收一个 HOF 并在类似集合的数据结构上执行,而不管内部通用类型如何......但我不能;这是举例说明问题的简单代码:

type EspecificList =
    | IntList of int list
    | BoolList of bool list

let listLength = function
    | IntList list -> List.length list
    | BoolList list -> List.length list

let listGenericFunc func = function
    | IntList list -> func list
    | BoolList list -> func list

listLength 按预期工作,但 listGenericFunc 没有;func专门针对第一种类型,而第二种类型则出现错误:添加通用类型注释(func: List<'a> -> 'b)也不起作用。

有什么办法可以避免这种情况并保持函数的通用精神?我可能错过了一些非常明显的东西,但我看不到它。

提前致谢!

编辑

好的,我在过去几天继续我的研究,虽然我打算提交一个关于我的特定域问题的更详细的问题,但我想分享我遇到的可能的解决方案,并询问你是否认为其中任何一个是更好或更多 FSharpish。我将按照找到它们的顺序列出解决方案。

1. 替代性反应

将函数传递 n 次,每种可能的类型一次;有或没有类型注解

let listGenericFunc (func: bool list -> 'b) (func': int list -> 'b) = function
    | BoolList list -> func list
    | IntList list -> func' list

listGenericFunc List.length List.length (IntList [1;2])

2. 静态成员和内联魔法

使用静态“apply”成员并为每个可能的函数定义类型,并与“applyer”内联函数一起使用。

type ListLength = ListLength with static member ( $ ) (ListLength, x) = x |> List.length

let inline applier f x = f $ x

let listGenericFuncInline func = function
    | IntList list -> applier func list
    | BoolList list -> applier func list

listGenericFuncInline ListLength (IntList [1; 2; 3]) // return 3
listGenericFuncInline ListLength (BoolList [true; false]) // return 2

这是对这个特殊的SO question的回应

3.隐藏的泛型

从上一个问题中,我找到了 Existential types 并搜索了一下我偶然发现了这篇文章。仅使用关于通用类型的文章的第一部分可以实现我想要的。

type UListFuncs<'ret> = abstract member Eval<'a> : ('a list) -> 'ret

let listLength : UListFuncs<int> =
    { new UListFuncs<int> with
        member __.Eval<'a> (x : 'a list) = x |> List.length }

let listGenericFuncUniversal (func : UListFuncs<'a>) = function
| IntList list -> func.Eval list
| BoolList list -> func.Eval list

listGenericFuncUniversal listLength (IntList [1; 2; 3]) // return 3
listGenericFuncUniversal listLength (BoolList [true; false]) // return 2

展示次数

我不知道哪一个是最好的选择;我觉得第二个有点别扭,因为每个函数都需要类型;我真的很喜欢第三个,不管增加了多少样板(这篇文章解释得很好,读起来也很有趣)。你觉得呢?你有没有什么想法?

标签: .nettypesf#higher-order-functions

解决方案


据我从给定的代码中可以看出,您使用的代码可能存在非明显差异的问题。

如果我们看

let listLength = function
    | IntList list -> List.length list
    | BoolList list -> List.length list

您会看到List.length对每个匹配案例的不同调用。该List.length函数是通用的,编译器可以在每个调用站点上找到正确的类型参数。

let listGenericFunc func = function
    | IntList list -> func list
    | BoolList list -> func list

另一方面,这个函数有点棘手。编译器将尝试解析为正确的类型,但无法做到这一点,因为类型参数只有一个“调用站点”和两种不同的情况。它将绑定到第一个案例并告诉您第二个案例不匹配。如果您更改通过匹配案例的顺序,您将看到编译器将相应地更改类型签名。

您可以做的一件事(我不推荐)是在应用函数之前将两种情况都设为相同的类型。

let listGenericFunc' (func: obj list -> 'b) (esp : EspecificList) =
    match esp with 
    | BoolList list -> list |> List.map box |> func
    | IntList list -> list |> List.map box |> func

这将起作用,因为您func将始终拥有表单obj list -> b。不过,这不是很好。我宁愿将您使用的 HOF 更改为每种情况都有一个拟合函数(它们也可以是相同的通用函数 - 这样就不会导致重复)。

let listGenericFunc (func: bool list -> 'b) (func': int list -> 'b) = function
    | BoolList list -> func list
    | IntList list -> func' list

listGenericFunc List.length List.length (IntList [1;2])

最终,使用数据结构的痛苦可能表明可能有更好的解决方案。不过,这取决于您非常具体的需求,您很可能会在迭代项目时发现这一点。


推荐阅读