关联reddit中的帖子。
在球拍中,我们可以使用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中是否有类似的语义?
答案 0 :(得分:3)
ML和Haskell实际上没有Lisp的&rest
(或类似Lisp的语言的类似功能)。抛开类型问题,定义函数的方式意味着没有好的方法来定义函数的“剩余参数”。
使用“rest”参数的主要两个应用是可变函数和函数包装器。 Reddit线程已经回答了如何进行可变参数函数(使用list参数),所以我认为这是关于函数包装器的,这里的东西可能会有些毛茸茸。
你遇到的根本问题是,对于没有专门使用元组或列表参数的ML函数,列表或参数元组的概念并不存在。例如,函数
let d x y = abs (x - y)
等同于函数
let d x = (fun y -> abs (x - y))
换句话说,(n + 1)-ary函数实际上是一个函数,当应用于单个参数时,产生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
值的一元函数?编译器无法分辨,你必须查看程序员的意图,所以你必须准确地告诉包装器要包装哪些部分。
当你有元组形式的参数时,你可以轻松定义memoization,因为元组只是一个参数。例如,让我们定义Ackermann函数,然后对其应用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
请注意,这个简单的示例实际上并没有将memoization应用于递归调用,而只是应用于顶级调用。但是,这个想法仍然应该清楚。
此外,如果转换涉及非平凡的多态行为,您可能需要一个仿函数而不是函数来表达转换。
使用一些样板文件,您也可以将其应用于f x y
表示法。例如,如果编写ack
来接受两个int参数而不是一对int,则可以编写:
let ack m n = memoize (fun (m, n) -> ack m n)
如果你觉得自己真的很有野心,你甚至可以写一个PPX重写器来编码它作为语法扩展点的一部分,这样你就可以编写如下内容:
let%memoize ack x y = ...
答案 1 :(得分:2)
在OCaml中没有像这样的东西。 Scheme中的rest参数(如R5RS 4.1.4第三篇文章 - link中所述),其中所有剩余参数都转换为列表。
这在OCaml中不起作用,列表元素必须是同一类型。
首先将参数转换为列表。然后,您可以定义类型in the OCaml manual 4.02 Par. 1.4
type number = Int of int | Float of float | Error;;
并使用模式匹配来解构参数。
答案 2 :(得分:1)
OCaml是一种静态类型语言。因此,与Scheme,Python和其他动态类型语言相比,它具有不同的风格。在Scheme中使用的方法将不像OCaml那样工作。此外,尝试将您的动态习惯转移到OCaml,Haskell,Rust或任何其他静态类型语言的编程通常是一个坏主意。这将导致难以理解和使用的非惯用代码。
从OCaml的角度来看,Scheme中的所有函数都接受一个类型为list的参数。在方案中,列表可以具有不同类型的值。在OCaml中,列表是单态的,并且为了将不同属的值放入同一列表中,您需要将它们强制转换为某些共同的祖先类型。您可以使用变体,就像@Str建议的那样。您还可以使用通用类型,它具有任何可能的值。 OCaml中有几个提供此类类型的库,例如Jane street的Univ库。实际上,scheme,python和其他动态语言也使用某种通用类型来表示它们的值。另一种方法,而不是使用一些祖先类型,将使用类型类,即,传递值一组函数,代表该值。目前,OCaml在官方版本中没有模块化含义,因此您需要明确传递它。您可以使用一流的模块或只使用记录。例如,要定义一个平均函数,你需要除法,求和,零(与求和运算相对的元素)和一个(与乘法无关的元素) - 一种在数学中称为字段的结构。
open Core_kernel.Std
module type Field = sig
type t
val zero : t
val one : t
val of_int : int -> t
val (+) : t -> t -> t
val (/) : t -> t -> t
end
有了这个,我们可以定义avg
函数:
let avg (type t) (module Field : Field with type t = t) xs : t =
Field.(List.fold ~f:(+) ~init:zero xs / of_int (List.length xs))
使用如下:
avg (module Int) [1;2;3;4]
模块化含义将成为OCaml的官方部分之后,我们将只编写avg [1;2;3;4]
并隐式找到相应的模块。
使用此框架,可以添加memoizing,如在扩展示例中(如果我理解正确)。可以使用记录甚至类(后者将允许在代数字段中表示子类型)而不是第一类模块。这仍然是OCaml的非惯用代码,因为它模糊了实现。
P.S。如果您有兴趣,在现代OCaml中为任意函数添加memoization的惯用解决方案是什么,那么它是使用扩展点,可以使用如下
let avg xs = <implementation>
[@@memoized (module Int)]