.net - 如何绕过通用高阶函数 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
展示次数
我不知道哪一个是最好的选择;我觉得第二个有点别扭,因为每个函数都需要类型;我真的很喜欢第三个,不管增加了多少样板(这篇文章解释得很好,读起来也很有趣)。你觉得呢?你有没有什么想法?
解决方案
据我从给定的代码中可以看出,您使用的代码可能存在非明显差异的问题。
如果我们看
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])
最终,使用数据结构的痛苦可能表明可能有更好的解决方案。不过,这取决于您非常具体的需求,您很可能会在迭代项目时发现这一点。
推荐阅读
- apache-spark - 使用 pyspark 将 StructType、ArrayType 转换/转换为 StringType(单值)
- java - 如何解决 MyBatis selectForUpdate 中的 indexOutOfBounds 错误?
- macos - Micronaut 使用 sdkman 安装,加入 micronaut 命令行权限被拒绝
- c# - 使用来自 web 服务响应的 c# lambda 表达式访问名称和标记
- javascript - 如何使用 JavaScript 更改 CSS 背景颜色?
- angular - Spring security addCorsMappings 没有效果
- google-apps-script - 具有特定开始和结束时间的 Google 日历活动创建
- assembly - “out”的操作数大小
- numpy - 从 3-d nd-array 中选择行
- prometheus-alertmanager - Prometheus Alert Manager 错误“component=cluster err="couldn't deduc an ad address" and "unable to initialize gossip mesh"