今天,我在浏览Jane Street的Core_kernel
模块的源代码时,发现了compose
函数:
(* The typical use case for these functions is to pass in functional arguments
and get functions as a result. For this reason, we tell the compiler where
to insert breakpoints in the argument-passing scheme. *)
let compose f g = (); fun x -> f (g x)
我会将compose
函数定义为:
let compose f g x = f (g x)
他们给出的定义CCD_ 4的方式是";由于compose
是一个以函数f
和g
为自变量并返回函数fun x -> f (g x)
的函数,因此他们定义compose
的方式与告诉编译器在自变量传递方案中在f
和g
之后但在x
之前插入断点的方式相同”
所以我有两个问题:
- 为什么我们在参数传递方案中需要断点
- 如果我们用正常的方式定义
compose
,会有什么不同
来自Haskell,这个约定对我来说没有任何意义。
这是一个高效的破解方法,可以避免在注释中指出的预期用例中部分应用程序的成本。
OCaml将curried函数编译为固定的arity构造,并在必要时使用闭包部分应用它们。这意味着这种arity的调用是有效的——没有闭包构造,只有一个函数调用。
对于fun x -> f (g x)
,在compose
中会有的闭包构造,但这将比部分应用程序更有效。部分应用程序生成的闭包通过一个包装器caml_curryN
,该包装器用于确保效果在正确的时间发生(如果闭包本身是部分应用的)。
编译器选择的固定arity是基于简单的语法分析的——本质上,一行中有多少个参数,中间没有任何东西。Jane St.的程序员已经使用它通过在"中间"参数中注入()
来选择他们想要的arity。
简而言之,let compose f g x = f (g x)
是一个不太理想的定义,因为它会导致compose f g
的常见双自变量情况是一个更昂贵的部分应用。
当然,从语义上讲,没有任何区别。
值得注意的是,OCaml中部分应用程序的编译有所改进,不再需要这种性能破解。