为具有更多限制性默认值的可选参数强制使用更广泛的类型
Posted
技术标签:
【中文标题】为具有更多限制性默认值的可选参数强制使用更广泛的类型【英文标题】:Force a broader type for optional argument with more restrictive default value 【发布时间】:2018-06-30 01:05:36 【问题描述】:如果identity
具有'a -> 'a
类型,有没有办法使可选参数f
足够灵活以具有'a -> 'b
类型,但仍使其默认为identity
?
An earlier question开始 准确地说出我的问题:
我想定义一个接受可选参数的函数,该参数是 一个函数('a -> 'b)。默认值应该是身份,即 实际上是 ('a -> 'a),但我认为没有理由不应该 与更一般的 ('a -> 'b) 兼容。
但是,该问题随后包含一个示例,该示例说明了一个较窄的问题。该问题的答案回应了更狭窄的问题。以下是一般问题的简单说明:
# let g1 ~f x = f x;;
val g1 : f:('a -> 'b) -> 'a -> 'b = <fun>
好的,这就是我想要的类型。但我想f
默认为identity
函数。这应该是可能的,因为identity
的类型为'a -> 'b
,其中'b
是'a
。但它不起作用:
# let g2 ?(f=identity) x = f x;;
val g2 : ?f:('a -> 'a) -> 'a -> 'a = <fun>
在identity
上添加类型规范没有帮助:
# let g3 ?(f=(identity:'a -> 'b)) x = f x;;
val g3 : ?f:('b -> 'b) -> 'b -> 'b = <fun>
编辑:在我发布这个问题后,我发现了this question,这确实与我的问题非常接近。因此,如果您愿意,请将我的问题标记为重复。但是,该问题的答案意味着我想做的事没有用处,这不是真的。以下是详细信息:
实际用例是一个从列表中选择一些元素的函数。可选的f
参数允许从每个元素中提取一些数据,并使用该数据来决定是否在结果中包含该特定列表元素。当元素被整个元素选中时,f
应该是identity
。我试图定义的实际功能是用于惰性列表。这是列表的简化版本,L
是 List
的别名:
let select ?(accessor=identity) keys vals =
let rec sel ks vs =
if ks = [] || vs = [] then []
else let k, v = L.hd ks, L.hd vs in
let v_key = accessor v in
if k = v_key then v::(sel (L.tl ks) (L.tl vs))
else if k > v_key then sel ks (L.tl vs) (* let vs catch up *)
else sel (L.tl ks) vs (* let ks catch up *)
in sel keys vals
简单使用:
# let vs = Batteries.List.range 1 `To 100;;
# let ks = [4; 10];;
# select ks vs;;
- : int list = [4; 10]
更一般的用途是当ks
的元素是具有整数键字段的记录时。然后accessor
函数将从记录类型映射到int
。
(是的,我知道hd
和tl
的使用有点不寻常。它更好地转化为惰性列表上下文。)
【问题讨论】:
【参考方案1】:当我问自己完全相同的问题时,我发现了你的问题,我找到了一个有点脏但仍然有效的解决方案:
你不能在功能层面上做,而是在结果层面上做:
external identity : 'a -> 'b = "%identity"
let to_array ?(f = identity) s = Array.init (String.length s) (fun i -> f s.[i])
然后当你不带任何参数地使用它时:
let c : char array = to_array "abc";;
val c : char array = [|'a'; 'b'; 'c'|]
显然,这会导致一些非常糟糕的行为:
# let c : string array = to_array "abc";;
Process OCaml segmentation fault
所以我不建议这样做,但这并非不可能
【讨论】:
秘密在于external
信任您编写的任何类型,因此这与使用Obj.magic
将默认值强制为比实际类型更通用的类型基本相同。换句话说,你可以做let apply ?(f = Obj.magic (fun x -> x)) x = f x
。然后,您可以使用非默认值调用它并且一切正常(apply f:succ 42
显示 - : int = 43
),或者您保留默认值然后结果的类型看起来比实际更一般(apply 42
显示 @987654330 @),您可以将结果限制为正确的类型,否则会发生不好的事情。
那么,你为什么说“你不能在功能层面做,而是在结果层面做”?
您实际上可以写?(f = Obj.magic)
,因为Obj.magic
是"%identity
,但我宁愿确定我的功能将始终是身份;-) 我说你不能在函数级别意味着只有在执行而不是编写时给出正确的类型时,预期的行为才有可能。选词不当,抱歉【参考方案2】:
我认为看待这个问题的方法是想象你的函数会有什么类型。您似乎在要求这种类型:
?f:('a -> 'b) -> 'a -> 'b
但是,这并没有捕捉到当您省略可选参数时,类型会恢复为 'a -> 'a
的想法。
相反,这个类型的意思是,如果你省略了可选参数,你就有了一个'a -> 'b
类型的函数。但是没有'a -> 'b
类型的格式良好的函数。这只能是永远不会返回(或类似)的函数的类型。
我对此的看法是,OCaml 类型系统无法表示您想要的功能,即使它确实有意义。
【讨论】:
谢谢杰弗里。但是,实际上具有'a -> 'b
类型的函数并不是用例所需要的。 g1
中f
的类型为'a -> 'b
,如果我将参数更改为~accessor
,则更长示例中accessor
的类型将为'a -> 'b
。这只是意味着这个参数可以是任何与该函数在外部函数中必须执行的操作兼容的函数。如果我要求的不能完成,那似乎只是 OCaml 的任意限制。 (或者不是很随意:我敢肯定这是有原因的。)
是的,我的意思是OCaml 中没有你想要的类型。据我所知。而且你只能在 OCaml 中定义具有 OCaml 类型的函数。
我不能在 OCaml 中做我想做的事,但在我看来,将其描述为“没有类型......为什么”的情况并不正确想。 'a -> 'b
是 OCaml 类型,使用具有该类型的非可选参数定义函数是没有问题的。 identity
具有该类型以及更具体的类型'a -> 'a
,因此我可以将identity
作为参数传递给这样的函数:let g f x = f x;; g identity 2 = 2;; - : int = 2
。然而,我不能将identity
作为类型为'a -> 'b
的可选参数的默认值,即使它是该类型的一个实例。
为整个函数指定一个类型,而不仅仅是参数。
啊,现在我明白你在说什么了。那么我想我应该说……有趣的是有('a -> 'b) -> 'a -> 'b
类型的函数,但没有?f:('a -> 'b) -> 'a -> 'b
类型的函数,尽管唯一的区别是在第二种情况下第一个参数是可选的。也许这是语法的产物?唯一允许您定义没有option
类型的可选参数的语法需要一个默认函数,其最具体的类型被视为参数的类型。【参考方案3】:
OCaml 根本不支持这一点。你不能编写一个类型根据是否传递了可选参数而优化的函数。
与链接问题中的某些答案不同,我同意这是一件合理的事情。实际上,键入诸如 Common Lisp 的序列函数(使用 :key
和 :test
)之类的东西似乎需要这样的东西。但是,OCaml 不是一种可以完成它的语言。
最合理的方法可能是编写两个函数,其中一个将访问器作为非可选参数,另一个提供identity
作为该参数:
let g f x = f x
let g_default x = g identity x
这只是有点笨拙,你不需要将g
中的逻辑实现两次。但是,将这种方法应用于多个可选参数的组合会变得很难看。
【讨论】:
谢谢gsg。我很好奇为什么不能这样做,但这与我问的问题不同。答案可能与 OCaml 类型系统的微妙之处有关。我怀疑这种事情在 Common Lisp 中要简单得多,因为它是动态类型的。 原因其实很简单。可选参数完全扩展为与option
类型匹配的模式。尝试使用_ option
类型的参数编写一个执行您想要的特化的函数,您会发现您也不能这样做。以上是关于为具有更多限制性默认值的可选参数强制使用更广泛的类型的主要内容,如果未能解决你的问题,请参考以下文章