是否可以在OCaml中定义rest参数?

时间:2016-03-13 08:34:31

标签: ocaml

关联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中是否有类似的语义?

3 个答案:

答案 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)]