OCaml 函数少传入一个参数

OCaml functions passing in one less argument

我正在寻找家庭作业的解决方案,代码实现了一个 OCaml 函数,该函数接受两个参数,但在调用它时,它只传递了一个参数。

let rec func2 r x = match r with
  | [] -> []
  | (nt,rhs)::t -> if nt = x then rhs::(func2 t x) else func2 t x;;

let func1 r = fun x -> func2 r x;;

我将通过调用 ( func1 awksub_rules )

根据如下语法规则调用 func1
let awksub_rules = [
  Expr, [T"("; N Expr; T")"];
  Expr, [N Num];
  Num, [T"0"];
  Num, [T"1"]
]

Expr 和 Num 只是已经定义的非终结符类型,T 符号表示终结符类型。

我很困惑,因为 func1 只接受 awksub_rules 作为参数,但函数声明有两个函数。

预期的输出将是

function 
  | Expr -> [[T"("; N Expr; T")"]; [N Num]]
  | Num -> [[T"0"]; [T"1"]]

我可以正确地看到 func1 returns 一个函数并且 func2 处理检查左侧(Expr 或 Num)是否相同以便它可以连接到列表。但我不知道传递给 x 的是什么。

当用一个参数调用 func1 时,它 returns 另一个函数,我们称它为 func3:

let func3 = func1 awksub_rules

此时,还没有参数x。这个新函数仍然需要传入这个参数。

调用这个新函数时,传入x的值,然后开始计算:

let result = func3 Num

我还想指出 func1func2 在逻辑上是等价的,因为 ML 中的机制称为“partial application”。也就是说,你可以在任何使用 func1 的地方使用 func2,并且效果相同:

let func3 = func2 awksub_rules
let result = func3 Num

Fyodor Soikin 的回答解释了为什么 func1func2 在逻辑上是相同的。但是,我不希望您放弃这种想法,即认为存在某种称为 "partial application" 的神奇语言功能。为了扩展您的思维,您需要了解 OCaml 中函数的工作方式是如何产生的。

在 ML 语言中,没有 "function that takes in two arguments" 这样的东西。 ML 中的每个函数 正好有一个参数(不是零,不是两个,不是三个;总是一个)。 OCaml 语法

let rec func2 r x = ...

的语法糖
let rec func2 = function r -> function x -> ...

即它只是一个函数的定义 returns 另一个函数。这就是为什么函数的类型将类似于 a -> b -> c-> 是 right-associative,因此与 a -> (b -> c) 相同)——它表示它是一个函数采用 a 和 returns 类型的函数 b -> c.

你去apply这个函数的时候,你以为传入两个参数,比如func2 foo bar,其实是(因为函数application是left-associative)(func2 foo) bar,即它首先将 func2 应用于表达式 foo,然后将其结果(一个函数)应用于表达式 bar.

通过接受一个参数并返回一个接受另一个参数的函数等来接受"multiple arguments"的做法被称为“currying”。 OCaml 和其他 ML 语言为定义柯里化函数提供了方便的语法(正如您在上面看到的,OCaml 中的通用语法 let 允许您简单地定义柯里化函数,只需将参数并排列出即可),而在其他语言中, 编写柯里化函数需要更冗长的语法。它是如此方便,以至于大多数时候我们会忘记它并将其视为多个参数。但记住它的真正含义总是很重要的。

(请注意,OCaml 编译器可能会或可能不会将柯里化函数优化为采用多个参数的机器代码函数,但这是您在语言级别不应该关心的实现细节。)