2016-03-13 80 views
2

相关帖子reddit是否可以在OCaml中定义休止参数?

在拍,我们可以定义一个函数rest argument为:

(define (avg . l) 
    (/ (apply + l) (length l))) 

我们可以通过调用这个函数:

> (avg 1 2 3) 
2 

有很多办法来解决所提到的这个特殊avg来自reddit的回复。

但是,如果我想要做一些复杂的东西:

(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

OCaml是一种强类型语言。在我的诚实看来,真正值得学习与OCaml类型系统一起工作而不是反对它。我不会尝试重新创建之前使用过的语言的无类型环境。经过OCaml的相当几年之后,我发现在强大的打字中有巨大的好处。 –

回答

3

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 = ... 
2

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;; 

并使用模式匹配来解构参数。

1

OCaml是一种静态类型语言。因此它与Scheme,Python和其他动态类型语言相比具有不同的风格。 Scheme中使用的方法在OCaml中不起作用。此外,尝试将您的动态习惯转换为OCaml,Haskell,Rust或任何其他静态类型语言的编程通常是一个糟糕的主意。这将导致一个非惯用的代码,这是很难理解和使用。

从OCaml的角度来看,Scheme中的所有函数都接受一个类型为list的参数。在方案中,列表可以具有不同类型的值。在OCaml列表中是单形的,为了将不同属的值放入同一个列表中,需要将它们强制为一些共同的祖先类型。你可以使用变种,比如@Str建议的。你也可以使用通用类型,这是由任何可能的价值居住。 OCaml中有几个库提供这种类型,例如Jane街的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] 

模块化implicits后,将成为OCaml中的正式组成部分,我们将只写avg [1;2;3;4]和相应的模块将隐式找到。

使用此框架,可以添加记忆,如在扩展示例中(如果我理解正确)。可以使用记录或甚至类(而后者将允许在代数字段中表示子类型)而不是一流的模块。这仍然是OCaml的非惯用代码,因为它掩盖了实现。

P.S.以防万一,如果你有兴趣,这将是添加记忆化在现代OCaml中的任意功能的惯用的解决方案,那么它是使用扩展点,可以使用如下

let avg xs = <implementation> 
[@@memoized (module Int)] 
+0

非常感谢@ivg,很难从你们所有人的答复中选择唯一的答案。我尝试使用你的例子:'let avg(type t)(module Field:t = t的字段)xs:t = Field(List.fold〜f:(+)〜init:zero xs/of_int(List .length xs))[@@ memoized(module Int)] ;;'。还有一个错误,我试图谷歌'[@@ memoized'和'OCaml memoization Extension points',没有结果。 – liweijian

+0

'[@@ memoized]'是一个假设的扩展,可以实现,但它还没有:) – ivg

相关问题