是否可以在 OCaml 中定义剩余参数?
Is it possible to define rest argument in OCaml?
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 中是否有类似的语义?
OCaml 中没有像 . Scheme 中的剩余参数(如 R5RS 4.1.4 第三个项目符号中所述 - link),其中所有剩余参数都转换为列表。
这在 OCaml 中不起作用,列表元素必须是同一类型。
首先将您的参数转换为列表。然后你可以定义你的类型,比如 in the OCaml manual 4.02 Par. 1.4
type number = Int of int | Float of float | Error;;
并使用模式匹配解构参数。
OCaml 是一种静态类型语言。因此它与 Scheme、Python 和其他动态类型语言相比具有不同的风味。在 Scheme 中使用的方法不会像在 OCaml 中那样工作。此外,尝试将您的动态习惯转移到使用 OCaml、Haskell、Rust 或任何其他静态类型语言进行编程通常不是一个好主意。这将导致非惯用代码,难以理解和使用。
从OCaml 的角度来看,Scheme 中的所有函数都接受一个列表类型的参数。在方案中,列表可以具有不同类型的值。在 OCaml 中,列表是单态的,为了将不同属的值放入同一个列表中,您需要将它们强制转换为一些共同的祖先类型。您可以为此使用变体,就像@Str 建议的那样。您还可以使用通用类型,它包含任何可能的值。 OCaml 中有多个库提供此类类型,例如 Jane street 的 Univ 库。事实上,scheme、python 和其他动态语言,也在使用某种通用类型来表示它们的值。另一种方法是使用 classes 类型,而不是使用某些祖先类型,即,将值传递给表示该值的一组函数。目前,OCaml 在正式版本中没有模块化隐含,因此您需要显式传递它。您可以使用 first-class 模块或仅使用记录。例如,要定义平均函数,您需要除法、求和、零(与求和运算中性的元素)和一(与乘法中性的元素)——数学中称为域的结构。
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]
在modular implicits成为OCaml的正式部分后,我们将只写avg [1;2;3;4]
并隐式地找到相应的模块。
使用此框架,可以添加记忆功能,如您的扩展示例(如果我理解正确的话)。可以使用记录甚至 classes(后者将允许在代数字段中表达子类型)而不是 first-class 模块。这仍然是 OCaml 的非惯用代码,因为它掩盖了实现。
P.S。以防万一,如果您有兴趣,在现代 OCaml 中向任意函数添加记忆化的惯用解决方案是什么,那么它就是使用扩展点,可以按如下方式使用
let avg xs = <implementation>
[@@memoized (module Int)]
ML 和 Haskell 并没有真正与 Lisp 的 &rest
对应(或类似 Lisp 语言的类似特性)。除了类型问题,函数的定义方式意味着没有好的方法来定义函数的 "the remaining arguments"。
使用 "rest" 参数的主要两个应用是可变参数函数和函数包装器。 Reddit 线程已经回答了如何执行可变函数(使用列表参数),所以我认为这是关于函数包装器的,这里的事情可能会变得有点毛茸茸。
您遇到的潜在问题是,对于不专门使用元组或列表参数的 ML 函数,实际上并不存在参数列表或元组的概念。例如,函数
let d x y = abs (x - y)
相当于函数
let d x = (fun y -> abs (x - y))
换句话说,一个 (n+1) 元函数实际上是一个函数,当应用于单个参数时,会产生一个 n 元函数。例如,d 0
returns 一个一元函数,描述从 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 = ...
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 中是否有类似的语义?
OCaml 中没有像 . Scheme 中的剩余参数(如 R5RS 4.1.4 第三个项目符号中所述 - link),其中所有剩余参数都转换为列表。
这在 OCaml 中不起作用,列表元素必须是同一类型。
首先将您的参数转换为列表。然后你可以定义你的类型,比如 in the OCaml manual 4.02 Par. 1.4
type number = Int of int | Float of float | Error;;
并使用模式匹配解构参数。
OCaml 是一种静态类型语言。因此它与 Scheme、Python 和其他动态类型语言相比具有不同的风味。在 Scheme 中使用的方法不会像在 OCaml 中那样工作。此外,尝试将您的动态习惯转移到使用 OCaml、Haskell、Rust 或任何其他静态类型语言进行编程通常不是一个好主意。这将导致非惯用代码,难以理解和使用。
从OCaml 的角度来看,Scheme 中的所有函数都接受一个列表类型的参数。在方案中,列表可以具有不同类型的值。在 OCaml 中,列表是单态的,为了将不同属的值放入同一个列表中,您需要将它们强制转换为一些共同的祖先类型。您可以为此使用变体,就像@Str 建议的那样。您还可以使用通用类型,它包含任何可能的值。 OCaml 中有多个库提供此类类型,例如 Jane street 的 Univ 库。事实上,scheme、python 和其他动态语言,也在使用某种通用类型来表示它们的值。另一种方法是使用 classes 类型,而不是使用某些祖先类型,即,将值传递给表示该值的一组函数。目前,OCaml 在正式版本中没有模块化隐含,因此您需要显式传递它。您可以使用 first-class 模块或仅使用记录。例如,要定义平均函数,您需要除法、求和、零(与求和运算中性的元素)和一(与乘法中性的元素)——数学中称为域的结构。
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]
在modular implicits成为OCaml的正式部分后,我们将只写avg [1;2;3;4]
并隐式地找到相应的模块。
使用此框架,可以添加记忆功能,如您的扩展示例(如果我理解正确的话)。可以使用记录甚至 classes(后者将允许在代数字段中表达子类型)而不是 first-class 模块。这仍然是 OCaml 的非惯用代码,因为它掩盖了实现。
P.S。以防万一,如果您有兴趣,在现代 OCaml 中向任意函数添加记忆化的惯用解决方案是什么,那么它就是使用扩展点,可以按如下方式使用
let avg xs = <implementation>
[@@memoized (module Int)]
ML 和 Haskell 并没有真正与 Lisp 的 &rest
对应(或类似 Lisp 语言的类似特性)。除了类型问题,函数的定义方式意味着没有好的方法来定义函数的 "the remaining arguments"。
使用 "rest" 参数的主要两个应用是可变参数函数和函数包装器。 Reddit 线程已经回答了如何执行可变函数(使用列表参数),所以我认为这是关于函数包装器的,这里的事情可能会变得有点毛茸茸。
您遇到的潜在问题是,对于不专门使用元组或列表参数的 ML 函数,实际上并不存在参数列表或元组的概念。例如,函数
let d x y = abs (x - y)
相当于函数
let d x = (fun y -> abs (x - y))
换句话说,一个 (n+1) 元函数实际上是一个函数,当应用于单个参数时,会产生一个 n 元函数。例如,d 0
returns 一个一元函数,描述从 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 = ...