文法

原始递归函数是一种函数式的计算模型,其文法定义如下:

zeroPRF0

sucPRF1

iN0i<nproj iPRFn

fPRFmgsPRFn[m]comp f gsPRFn

fPRFngPRFn+2rec f gPRFn+1

其中 PRFn 中的 n 是一个自然数,代表函数的参数个数。

PRFn[m] 是一个长度 为 m 的列表(可以由 , 或者任何你喜欢的符号分隔,毕竟文法不重要),其中的元素是 PRFn

语义

人话语义

每个原始递归函数都接受一个自然数参数列表,并返回一个自然数。

zerosuc 好理解,不多赘述。

proj i 就是取参数列表中的第 i 个元素。

comp f gs 就是先调用 gs 中的每一个函数(使用的就是当前的参数列表),然后用结果作为参数再调用 f。 可以理解为 fgs 的复合 fgs

用 JS 表示就是:

const comp = function(f, gs, args) {
  return f(...gs.map(g => g(args))));
}

rec f g (假设参数列表为 (n,x1,x2,,xn))就是

  1. 当前结果为 f(x1,x2,,xn)
  2. 将当前结果与调用 g 的次数作为参数,调用 g,直到调用 g 的次数达到 n 次。

用 JS 表示就是:

const rec = function(f, g, n, args) {
  let result = f(args);
  for (let i = 0; i < n; i++) {
    result = g(result, i);
  }
  return result;
}

指称语义

其中 index 定义如下:

注意这里的参数列表是反着写的,不过这只是一个 design choice, 对理解“这是什么”影响不大。

大步 operational 语义

index 定义同上。

计算能力

直觉地,我们可以看出“原始递归函数” 这个计算模型应当能计算 所有 在 固定次数的循环 中能计算出来的 函数的 值。

不能算什么

self-interpreter

我们无法用 原始递归函数 来编写 原始递归函数 的解释器。

形式化地,不存在这样的 原始递归函数 eval

fPRF1,n.eval(nil,(f,n))=f(nil,n)

其中 (f,n)fn 编码到的自然数。

这个编码方式可以参考 哥德尔数

我们假设 (a,b) 被编码为 2a3b

证明思路是构造 g = comp suc (nil, comp eval (nil, f)),其中 f(nil,n)=2n3n

我们可以得到 g(nil,code g)=1+g(nil,code g)(其中 code 是将 PRF1 编码为自然数的函数),这是一个矛盾。

Iterated exponentials

↑∈𝑚zero 𝑘=𝑚𝑘𝑚suc n zero=1𝑚suc n suc k=𝑚𝑛(𝑚suc n 𝑘)

Ackermann 函数

ack ∈ ℕ × ℕ → ℕ
ack (zero, n) = suc n
ack (suc m, zero) = ack (m, suc zero)
ack (suc m, suc n) = ack (m, ack (suc m, n))

和其他计算模型的关系

和 PRF 相似,但比 PRF 更弱的计算模型是 elementary recursive function,说实话它并不递归。

和 PRF 相似,但比 PRF 更强的计算模型是 (General)递归函数(一般简称为 RF)。

简单来说,相比 PRF,RF 添加了一个运算 min,用于搜索某个函数在其他参数给定的情况下,关于第一个参数的最小零点, 显然这个运算是一个 partial function,这个零点可能根本不存在。 RF 的计算能力就和图灵机等价了。

PRF 能计算所有算数阶层Δ00 阶层中的公式,原因是 Δ00 中所有量词都是有界的,通过 rec 我们可以逐个检查每个值。 实际上 PRF 强于 Δ00

不那么严格的,“只允许使用有界循环”的面向过程程序强于 Δ00, 等于PRF,两者等价的形式化和证明见 Schwichtenberg and Wainer's Proofs and Computations,section 2.5。