当我学习OCaml essentials时,我被告知OCaml中的每个函数实际上只是一个只有一个参数的函数。多参数函数实际上是一个函数,它接受一个参数并返回一个函数,该函数接受下一个参数并返回....
这是好事,我明白了。
所以我的问题是:
案例1
如果我这样做
let plus x y = x + y
在OCaml编译时,OCaml会将其更改为let plus = fun x -> fun y -> x + y
吗?
或其他方式
案例2
如果我这样做
let plus = fun x -> fun y -> x + y
OCaml会将其转换为let plus x y = x + y
?
哪种情况属实?好的或优化的OCaml编译器在正确的情况下做了什么?
此外,如果 case 2 为真,那么OCaml正在考虑做什么呢?我的意思是它实际上是相反的,对吗?
此问题实际上与Understand Core's `Fn.const`
有关答案 0 :(得分:7)
let plus x y = x + y
和let plus = fun x -> fun y -> x + y
都将编译为相同的代码:
camlPlus__plus:
leaq -1(%rax, %rbx), %rax
ret
是的,正好是两个汇编指令,没有任何序言和结尾。
OCaml编译器执行几个优化步骤,实际上"认为"在不同的类别。例如,两个函数都用相同的lambda代码表示:
(function x y (+ x y))
我认为,根据上面的lambda,您可能会认为OCaml编译器转换为非curried版本。
我还想补充一下关于核心const
功能的几句话。假设我们有const函数的两个语义等价的表示:
let const_xxx c = (); fun _ -> c
let const_yyy c _ = c
以lambda形式表示为:
(function c (seq 0a (function param c))) ; const_xxx
(function c param c) ; const_yyy
因此,正如您所看到的,const_xxx
确实以curry形式编译。
但最有趣的问题是,为什么值得在一个如此模糊的代码中编写它。也许在汇编输出中有一些线索(amd64):
camlPlus__const_xxx_1008:
subq $8, %rsp
.L101:
movq %rax, %rbx ; save c into %rbx (it was in %rax)
.L102:
subq $32, %r15 ; allocate memory for a closure
movq caml_young_limit(%rip), %rax ; check
cmpq (%rax), %r15 ; that we have memory, if not
jb .L103 ; then free heap and go back
leaq 8(%r15), %rax ; load closure address to %rax
movq $3319, -8(%rax)
movq camlPlus__fun_1027(%rip), %rdi
movq %rdi, (%rax)
movq $3, 8(%rax)
movq %rbx, 16(%rax) ; store parameter c in the closure
addq $8, %rsp
ret ; return the closure
.L103: call caml_call_gc@PLT
.L104: jmp .L102
const_yyy
怎么样?它编译为:
camlPlus__const_yyy_1010:
ret
只需返回参数即可。因此,假设实际的优化点是在const_xxx
中,闭包创建是在函数内编译的,应该很快。另一方面,const_yyy
并不期望以curry方式调用,所以如果你在没有所有需要的参数的情况下调用它,那么编译器需要添加创建闭包的代码const_yyy
部分应用程序(即每次调用const_xxx
时执行const_xxx x
中的所有操作)。
总而言之,const
优化会创建一个针对部分应用程序进行优化的函数。虽然,它带来了成本。如果使用所有参数调用它们,则非优化的const
函数将优于优化的函数。 (实际上,当我用两个args应用它时,我的参数甚至会调用const_yyy
。
答案 1 :(得分:5)
就OCaml语言的语义而言,这两个定义都是完全等同于curry函数的定义。在OCaml语言的语义中没有多参数函数。
然而,实施是另一回事。具体而言,OCaml语言的当前实现在其内部表示中支持多参数函数。当以某种方式定义curried函数(即let f x y = ...
或let f = fn x -> fn y -> ...
)时,这将在内部编译为多参数函数。但是,如果它的定义不同(如链接问题中的let f x = (); fn y -> ...
),它将被编译为curried函数。这只是一种优化,不会以任何方式影响语言的语义。定义curried函数的所有三种方法在语义上都是等价的。
关于什么变成什么的具体问题:由于转换不是从一个OCaml代码转换为另一个OCaml代码,而是从OCaml代码到内部表示,我认为最准确描述它的方式是说OCaml编译器在内部将let plus x y = x + y
和let plus = fn x -> fn y -> x + y
都转换成相同的东西,而不是将它变为另一个。
答案 2 :(得分:4)
案例1和案例2都是curry函数。这是非咖喱版:
let plus (x, y) = x + y
答案 3 :(得分:3)
好的,我了解到本机编译器将优化您的代码,我期望它做什么。但这是字节码编译器:
let plus1 x y = x + y
let plus2 = fun x y -> x + y
let plus3 = function x -> function y -> x + y
用ocamlc -c -dinstr temp.ml
处理的给了我:
branch L4
restart
L1: grab 1
acc 1
push
acc 1
addint
return 2
restart
L2: grab 1
acc 1
push
acc 1
addint
return 2
restart
L3: grab 1
acc 1
push
acc 1
addint
return 2
表示结果完全相同,它只是语法差异。这些论点是一个接一个的。
顺便说一下,还有一个语法点:fun
可以用n个参数编写,function
只能用一个参数编写。
从概念的角度来看,我非常赞成function x -> function y ->
而不是其他人。