如何让OCaml可选参数f支持'a->'b类型并默认使用identity?
解决OCaml可选参数默认值的多态类型约束问题
这个问题确实是OCaml类型推导里一个有点 tricky 的点——你想要的类型理论上是成立的,但OCaml对可选参数的类型推导规则会默认根据默认值的类型收窄整个参数的类型范围,不过我们有直接的办法解决它!
为什么之前的尝试失败?
当你定义let g2 ?(f=identity) x = f x时,OCaml的类型推导会优先基于默认值identity的类型('a -> 'a)来约束可选参数f的类型。它会自动统一类型变量,把'b(f的返回类型)和'a(f的参数类型)绑定在一起,最终得到的函数类型就变成了?f:('a -> 'a) -> 'a -> 'a,而不是你期望的更通用的?f:('a -> 'b) -> 'a -> 'b。
就算给identity加类型标注('a -> 'b)也没用,因为OCaml会立刻把这个多态类型实例化,最终还是会把'a和'b统一成同一个类型变量。
解决方案:显式指定函数的完整类型
要绕开这个推导限制,最简单的方法是显式声明函数的完整类型,告诉OCaml我们想要的通用类型,而不是让它自动推导。这样OCaml就会接受默认值identity(因为'a -> 'a是'a -> 'b的一个合法特例,当'b = 'a时成立)。
比如你想要的g函数可以这样定义:
let g : ?f:('a -> 'b) -> 'a -> 'b = fun ?(f=identity) x -> f x
验证一下类型:
# let g : ?f:('a -> 'b) -> 'a -> 'b = fun ?(f=identity) x -> f x;; val g : ?f:('a -> 'b) -> 'a -> 'b = <fun>
完全符合你的需求!测试使用场景:
- 不传入
f时,自动使用identity,类型为'a -> 'a:# g 10;; - : int = 10 - 传入自定义的
f(比如string_of_int)时,类型自动适配为int -> string:# g ~f:string_of_int 10;; - : string = "10"
适配到你的select函数场景
把这个方法用到你实际的筛选函数上,只需要给select加上显式的类型声明即可:
let select : ?accessor:('v -> 'k) -> 'k list -> 'v list -> 'v list = fun ?(accessor=identity) keys vals -> let rec sel ks vs = if ks = [] || vs = [] then [] else let k, v = List.hd ks, List.hd vs in let v_key = accessor v in if k = v_key then v::(sel (List.tl ks) (List.tl vs)) else if k > v_key then sel ks (List.tl vs) (* let vs catch up *) else sel (List.tl ks) vs (* let ks catch up *) in sel keys vals
现在测试你的示例场景:
- 默认使用
identity,直接根据元素本身筛选:# let vs = Batteries.List.range 1 `To 100;; # let ks = [4; 10];; # select ks vs;; - : int list = [4; 10] - 传入自定义
accessor处理记录类型(假设你有这样的记录定义):type user = { id : int; name : string } # let users = [{id=4; name="Alice"}; {id=5; name="Bob"}; {id=10; name="Charlie"}];; # let ks = [4; 10];; # select ~accessor:(fun u -> u.id) ks users;; - : user list = [{id=4; name="Alice"}; {id=10; name="Charlie"}]
完美满足你的需求!
内容的提问来源于stack exchange,提问作者Mars
相关产品推荐
相关产品推荐

