ML和Haskell并没有与Lisp的&rest
(或Lisp-like语言的类似功能)相对应。抛开类型问题,定义函数的方式意味着没有好方法来定义函数的“剩余参数”。
使用“rest”参数的主要两个应用程序是可变参数函数和函数包装器。 Reddit线程已经回答了如何做一个可变参数函数(使用列表参数),所以我认为这是关于函数包装器的,这里可能会有些毛病。
您所拥有的底层问题是,对于不特别使用元组或列表参数的ML函数,实际上并不存在参数列表或元组元组的概念。例如,功能
let d x y = abs (x - y)
相当于功能
let d x = (fun y -> abs (x - y))
换言之,第(n + 1)进制函数实际上是一个函数,当施加于一个参数,产生一个n元函数。例如,d 0
返回描述与0
的距离的一元函数。
如果要对参数元组操作,则需要指定它们的方式:
let d (x, y) = abs (x - y)
你可以接着用(例如)调用此d(3, 5)
,而不是d 3 5
。请注意,优化OCaml编译器通常应为这两种情况生成相同的代码。
您可以轻松地区分不同的类型。第一个函数(d x y
)具有类型
int -> int -> int
,而第二个(d(x, y)
)具有
int * int -> int
元数成为前者的情况下真的模糊的概念类型:我们有一个二元函数返回int
类型的值或返回值为int -> int
的一元函数?编译器不能说,你必须看看程序员的意图,所以你必须告诉一个包装究竟哪些部分要包装。
当您以元组形式存在参数时,您可以轻松定义memoization,因为元组只是一个参数。例如,让我们定义阿克曼函数,然后运用记忆化它:
let rec ack = function
| (0, n) -> n + 1
| (m, 0) -> ack (m-1, 1)
| (m, n) -> ack (m-1, ack(m, n-1))
let memoize f =
let memo_table = Hashtbl.create 0 in
let f' x = try
Hashtbl.find memo_table x
with Not_found -> begin
let y = f x in Hashtbl.add memo_table x y; y
end in f'
let ack = memoize ack
注意,这个简单的例子并不实际应用记忆化的递归调用,但仅限于顶级调用。但是,这个想法应该还是很清楚的。另外,如果转换涉及非平凡的多态行为,则可能需要函数而不是函数来表示转换。
使用一些样板,您也可以将其应用于f x y
表示法。例如,如果你已经ack
书面接受两个int参数,而不是一对整数的,然后你可以写:
let ack m n = memoize (fun (m, n) -> ack m n)
如果你觉得真的雄心勃勃,你甚至可以写一个PPX重写编码以此为作为语法扩展点的一部分,以便您可以编写如下内容:
let%memoize ack x y = ...
OCaml是一种强类型语言。在我的诚实看来,真正值得学习与OCaml类型系统一起工作而不是反对它。我不会尝试重新创建之前使用过的语言的无类型环境。经过OCaml的相当几年之后,我发现在强大的打字中有巨大的好处。 –