相关线程在 reddit .
在racket中,我们可以用rest argument定义一个函数作为:
(define (avg . l)
(/ (apply + l) (length l)))
我们可以这样调用这个函数:
> (avg 1 2 3)
2
有很多方法可以解决 reddit 回复中提到的这个特定的 avg
。
但是如果我想做一些更复杂的事情,比如:
(define *memoize-tbl* (make-hasheq))
(define (bind fn . args)
(let ([res (apply fn args)])
(hash-set! *memoize-tbl*
(equal-hash-code (cons fn args))
res)
res))
(define (f1 loi i s)
(+
(length loi)
i
(string-length s)))
(bind f1 '(1 2 3) 8 "hi")
我们可以看到 bind
函数不关心 fn
有多少个参数,参数的类型可以是任何类型:整数、列表、字符串。
我想知道 OCaml 中是否有类似的语义?
最佳答案
ML 和 Haskell 并没有真正与 Lisp 的 &rest
(或类 Lisp 语言的类似功能)相对应。撇开类型问题不谈,函数的定义方式意味着没有好的方法来定义函数的“剩余参数”。
使用“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
Arity 在前一种情况下变成了一个非常模糊的概念:我们有一个二元函数返回类型 int
的值还是一个一元函数返回 int -> int
?编译器无法分辨,你必须看程序员的意图,所以你必须告诉包装器确切地包装哪些部分。
当你有元组形式的参数时,你可以很容易地定义内存,因为元组只是一个单一的参数。例如,让我们定义 Ackermann 函数,然后对其应用内存:
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 参数而不是一对 int,那么您可以这样写:
let ack m n = memoize (fun (m, n) -> ack m n)
如果你真的雄心勃勃,你甚至可以编写一个 PPX 重写器将其编码为语法扩展点的一部分,这样你就可以编写如下内容:
let%memoize ack x y = ...
关于ocaml - 是否可以在 OCaml 中定义剩余参数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35968221/