如何绕过泛型高阶函数 F# 的类型推断限制



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

我需要使用类似集合的结构,该结构应用作 Map 中的值。此集合具有泛型类型,映射应包含这些集合的任何类型的(整数、布尔值、字符串等(。鉴于这不可能直接实现(因为泛型类型限制在 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 次 1;带或不带类型批注

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"成员,并结合"apper"内联函数为每个可能的函数定义类型。

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问题的回应

3. 隐藏泛型类型

从最后一个问题中,我找到了存在主义类型,搜索了一下,我偶然发现了这篇文章。仅使用有关通用类型的文章的第一部分就可以完成我想要的。

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

印象

我不知道他们中的哪一个是最好的选择;我觉得第二个有点尴尬,因为每个函数都需要类型;无论添加的样板如何,我都非常喜欢第三个(这篇文章解释得很好,读起来也很有趣(。你有什么想法?

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

如果我们看看

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

您将看到每个匹配案例的不同调用List.lengthList.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])

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

let listGenericFunc (func: EspecificList -> 'a) (l: EspecificList) =
match l with
| IntList _ -> func l
| BoolList _ -> func l

这似乎只是另一个毫无意义的变体,因为您不妨直接用l来称呼func。因此,看起来我们又回到了@WalternativE的结论。

顺便说一句,这根本不是第一次有人问泛型和 DU 以这种方式组合,而且看起来它不是故意的。也许比我聪明的人可以详细说明。

最新更新