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

问题描述 投票:1回答:2

这个问题似乎很愚蠢,但我在网上找不到任何类似的内容。所以我走了。

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

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)之类的泛型类型注释中也不起作用。

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

提前感谢!

编辑

[好吧,我在过去的几天里继续了我的研究,尽管我计划提交有关我的特定领域问题的更详细的问题,但我想分享我遇到的可能解决方案,并询问您是否认为他们或多或少是沙哑的。我将按照找到它们的顺序列出解决方案。

1。 WalternativE回应

对每种可能的类型将函数传递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。隐藏的通用类型

[从上一个问题开始,我发现了存在类型,然后我偶然发现了this article。仅使用有关通用类型的文章的第一部分就可以完成我想要的事情。

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

展示次数

我不知道哪个是最好的选择;我觉得第二个函数有点尴尬,因为每个函数都需要这种类型。无论添加了什么样板,我都真的很喜欢第三个(这篇文章很好地解释了,阅读起来也很有趣)。您有什么想法?

.net types f# higher-order-functions
2个回答
3
投票

据我从给定的代码中可以看到的,您使用的代码之间的非明显差异可能是一个问题。

如果我们看

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])

最终,使用数据结构的痛苦可能表明可能存在更好的解决方案。不过,这取决于您的特定需求,并且您很可能会在遍历项目时发现这一点。


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

这似乎只是另一个毫无意义的变化,因为您最好直接用func调用l。因此,看来我们又回到了@WalternativE的结论。

Btw,这根本不是第一次有人问过以这种方式组合使用的泛型和DU,而且看起来这并非本来就应该如此。也许有人比我更聪明。

© www.soinside.com 2019 - 2024. All rights reserved.