自从我编写了OCaml以来已经有一段时间了,我遇到了这个问题,听起来很简单,但我有一个解决问题的心理障碍:
编写一个函数,它接受一个带有可变数量参数的函数f
,这些参数返回一个布尔值(即f
的类型为'a -> 'b -> 'c -> ... -> bool
)并返回一个函数{{1} }表示对g
的否定(即所有有效参数集的f
)。
它的灵感来自以下代码块,它解决了Javascript中的问题:
(f x1 x2 .. xn) == not (g x1 x2 .. xn)
(来自http://eloquentjavascript.net/1st_edition/chapter6.html)
但是,我没有看到在OCaml中实现此功能的方法("参数"关键字或等效项不可用),因为函数function negate(func) {
return function() {
return !func.apply(null, arguments);
};
}
没有预设的参数数量。我找到了关于处理具有可变数量参数的函数的链接(例如https://blogs.janestreet.com/variable-argument-functions/),但我想知道是否有更简单/更自然的'处理这个具体问题的方法。
答案 0 :(得分:4)
我是一名JavaScript程序员,我一直坚持variadic arguments are harmful。如果我们在JavaScript中没有变量函数(只是远离arguments
对象),那么在Hindley Milner类型系统中可用的JavaScript中的每个函数(减去API特定函数,如DOM函数)都可以很容易转换成OCaml中的等效函数。
那么apply
函数的OCaml等价物是什么?我相信它的正常功能应用:
let apply f x = f x (* equivalent of apply in JavaScript *)
普通函数应用程序如何与JavaScript中的apply
函数等效?考虑:
let s f g x = f x (g x) (* the S combinator from the SKI combinator calculus *)
此函数将使用JavaScript编写,如下所示:
var s = function (f) {
return function (g) {
return function (x) {
return f(x)(g(x));
};
};
};
请注意,每个函数定义和函数调用都是以curry形式显式写的。
这是JavaScript和OCaml之间的区别:
因此,让我们来看看S组合子的未经证实的变体。首先,OCaml:
let s (f, g, x) = f (x, g (x)) (* sml convention is to use uncurried functions *)
JavaScript中的等价物:
var s = function (f, g, x) {
return f(x, g(x));
};
请注意,正常功能应用程序在OCaml和JavaScript中都是相同的。对于curried函数:
let result = s f g x (* equivalent to `((s f) g) x` *)
JavaScript中的等价物:
var result = s(f)(g)(x);
对于未经证实的功能:
let result = s (f, g, x)
JavaScript中的等价物:
var result = s(f, g, x);
那么apply
函数怎么样?这相当于正常的功能应用程序?
在OCaml中,你可以这样做:
let args = (f, g, x) (* args is a tuple *)
let result = s args (* normal function application *)
JavaScript中的等价物是:
var args = [f, g, x]; // args is an array
var result = s.apply(null, args); // normal function application
正如您所看到的,OCaml中的元组相当于JavaScript中的数组。 JavaScript中的数组是通用的。它们可以用作列表或元组,具体取决于上下文。
赋予args
的{{1}}参数可以是任何类似数组的对象,并将其视为单个元组参数。 JavaScript中的每个函数都可以被认为是单个参数函数。 JavaScript中的多参数函数可以被认为是单参数元组参数函数。 JavaScript的apply
函数只是普通函数应用程序的一种特殊形式。
那么这意味着什么?考虑:
apply
如果我们认为var negate = function (f) {
return function () {
return !f.apply(null, arguments);
};
};
是内部函数的隐式参数,那么OCaml中上述函数的等价物是:
arguments
这可以简化为:
let negate f = fun arguments -> not (f arguments) (* arguments is explicit *)
现在,您可能会说这只适用于单个参数函数。事实并非如此。 let negate f x = not (f x)
的类型签名是:
negate
因此,它可以适用于包含元组的任何类型val negate : ('a -> bool) -> 'a -> bool
。这相当于JavaScript,其中多参数函数只是单参数元组参数函数。
最后,唯一真正的问题是将curried函数转换为未经验证的函数,以便'a
可以使用它们。不幸的是,在OCaml中没有发布函数的通用方法。所以你需要一系列函数来negate
几个arities的curried函数:
uncurry
在否定功能后,你可以let uncurry2 f (x, y) = f x y
let uncurry3 f (x, y, z) = f x y z
.
.
.
.
回来。但是,就像使用curry
一样,通常无法uncurry
一个函数。因此,您再次需要一系列curry
函数:
curry
创建通用let curry2 f x y = f (x, y)
let curry3 f x y z = f (x, y, z)
.
.
.
.
或curry
函数的唯一方法是使用动态类型语言(如Lisp或JavaScript)或依赖类型语言(如Idris或Agda)。 OCaml的类型系统(Hindley Milner型系统)限制性太强,不允许这样的功能。
答案 1 :(得分:2)
在ocaml中接受多个参数的函数实际上是一个接受一个参数并返回另一个函数的函数。 这是咖喱。
你想要做的事情是使用未经验证的函数,即只接受一个参数的函数(可能是一个元组):
例如,您需要f : (a * b * c) -> bool
而不是f : a -> b -> c -> bool
。但你必须手动"改变你的功能。
您可以使用let uncurry f (x,y) = f x y
这样的功能,但这只是传输问题,因为您必须为任意数量的参数执行此操作。
也许你可以否定将参数列表作为参数的函数。 我的意思是我不知道你要做什么的具体细节。
答案 2 :(得分:0)
这并不难,但你必须手工编写(并在调用网站,选择)每个arity的明确定义,因为OCaml缺乏必要的机制来抽象不同arities的类似定义。
请注意,这是OCaml代码中已存在的模式:请参阅List.map(2)
,List.iter(2)
等。
定义可能如下所示:
let negate f = (fun a -> not (f a))
let negate2 f = (fun a b -> not (f a b))
let negate3 f = (fun a b c -> not (f a b c))
(* etc *)
请注意,允许这种多态的类型系统是可以想象的:实际上Typed Racket可能能够表达这个定义。