德布鲁因数码
De Bruijn Numerals

原始链接: https://text.marvinborner.de/2023-08-22-22.html

## 德布鲁因数码:探索 本文介绍“德布鲁因数码”,一种基于嵌套德布鲁因索引的纯 lambda 演算中自然数的全新编码方式。一个数 *n* 被表示为 `λ^(S(n)) n`,其中 S(n) 是后继函数。虽然优雅,但该系统面临一个根本限制:它无法“充分”(adequate)——这意味着它无法同时支持后继、前驱和零测试——这是由于所需抽象层级随着数字增大而无限增加,Wadsworth (1980) 已经证明了这一点。 尽管如此,德布鲁因数码提供了独特的优势。后继函数易于定义,前驱函数也同样可行。算术运算,例如加法,可以通过巧妙地利用德布鲁因索引的移位来实现。作者用一个简洁的加法函数演示了这一点。 一个关键的见解将德布鲁因数码与 Church 数码联系起来。两者之间可以进行转换,利用 Church 编码的结构。这使得在 Church 数码上定义的运算可以转换为德布鲁因表示。作者强调了实际应用,尤其是在使用 Church 元组时,德布鲁因数码简化了元素选择和操作,甚至可以实现诸如在不知道其大小时在元组内移动元素的操作——这对于标准的 Church 列表表示来说是一项困难的任务。代码示例可在标准库 ("bruijn") 中找到。作者邀请进一步探索和贡献,以扩展此编码的功能。

这篇 Hacker News 帖子链接到一篇关于 **De Bruijn 数字** 以及 Christopher Wadsworth 对数字系统属性相关分析的文章。 Wadsworth 将“充分”的数字系统定义为能够支持后继、前驱和零测试函数——这些是算术运算所必需的。 关键在于,一个数字系统是充分的 *当且仅当* 它可以被转换为 **Church 数字** 并从 Church 数字转换回来。 文章详细介绍了转换过程:从 Church 数字转换需要 `N0` 和 `Nsucc` 函数,而转换为 Church 数字则需要 `Nzero?` 和 `Npred`,并隐式地利用不动点组合子。 基本上,讨论的中心是数字系统为了算术运算在功能上完备的基本要求。
相关文章

原文
de Bruijn Numerals
assumptions in this post
  • understanding of pure lambda calculus
  • zero-based de Bruijn indices instead of named variables
  • nn consecutive lambdas are written as λn\lambda^n
  • ne\langle n\rangle_e
  • S(n)S(n) is the Peano successor function

There are many ways to encode numbers and do arithmetic in the pure lambda calculus, for example using unary Church numerals, various nn-ary representations, and the Scott encoding.

A method that I have not seen yet is an encoding by reference depth. Specifically, I refer to a nested de Bruijn index that, by itself, encodes the number:

nd=λS(n)n\langle n\rangle_d=\lambda^{S(n)}n

For example, the decimal number 44 would be encoded as 4d=λ54\langle4\rangle_d=\lambda^54

However, during my experiments with this encoding, I made an unfortunate discovery. While reading up on the theory behind optimal reduction in some ancient book I found in the library, I stumbled upon the work “Some Unusual Numeral Systems” by Wadsworth (1980), which was coincidentally published in the same book as Lévy’s legendary paper on optimal reduction. In this work, Christopher Wadsworth analyzed different properties of numeral systems and the requirements they have to fulfill to be useful for arithmetic.

Specifically, he calls a numeral system adequate if it allows for a successor (succ) function, predecessor (pred) function, and a zero? function yielding a true (false) encoding when a number is zero (or not). He then went on to show that there can not exist an adequate numeral system encoding numbers as ne=λknEn,\langle n\rangle_e=\lambda^{k_n}E_n,

Unfortunately, a zero? function is required in order to convert any number from this numeral system to another system. So, even with these limitations, can the “de Bruijn numerals” still be useful for anything? What do other arithmetic operations look like?

The simplest operation is the succ function, which is minimally defined as succ=λ32 1=b k\texttt{succ}=\lambda^32\ 1=\texttt{b}\ \texttt{k}

Proof

To prove: nN0:succ ndS(n)d\forall n\in\mathbb{N}_0: \texttt{succ}\ \langle n\rangle_d\overset{*}{\leadsto}\langle S(n)\rangle_d

Proof by β\beta-reduction ({\leadsto}): succ nd= (λ32 1) λS(n)n λ2(λS(n)n) 1 λ2λnS(n)=λS(S(n))S(n)=S(n)d \begin{align*} \texttt{succ}\ \langle n\rangle_d=\ &(\lambda^32\ 1)\ \lambda^{S(n)}n\\ \leadsto\ &\lambda^2(\lambda^{S(n)}n)\ 1\\ \leadsto\ &\lambda^2\lambda^nS(n)=\lambda^{S(S(n))}S(n)=\langle S(n)\rangle_d \end{align*}

The pred function can be implemented similarly: pred=λ21 0 0=w\texttt{pred}=\lambda^21\ 0\ 0=\texttt{w}

Proof

To prove: nN0:pred S(n)dnd\forall n\in\mathbb{N}_0: \texttt{pred}\ \langle S(n)\rangle_d\overset{*}{\leadsto}\langle n\rangle_d

Proof by β\beta-reduction ({\leadsto}): pred S(n)d= (λ21 0 0) λS(S(n))S(n) λ(λS(S(n))S(n)) 0 0 λ(λS(n)S(n)) 0 λλnn=λS(n)n=nd \begin{align*} \texttt{pred}\ \langle S(n)\rangle_d=\ &(\lambda^21\ 0\ 0)\ \lambda^{S(S(n))}S(n)\\ \leadsto\ &\lambda(\lambda^{S(S(n))}S(n))\ 0\ 0\\ \leadsto\ &\lambda(\lambda^{S(n)}S(n))\ 0\\ \leadsto\ &\lambda\lambda^nn=\lambda^{S(n)}n=\langle n\rangle_d \end{align*}

They both use the fact that the outermost abstraction is always bound to the inner de Bruijn index. Substituting the index with a reference to a fresh abstraction results in incrementing/decrementing behavior (utilizing the shift procedure of β\beta-reduction with de Bruijn indices)

One problem of pred is its application to 0d\langle0\rangle_d

Now, normally further operations could be defined by recursion ending with a truthy zero?. Of course, this is not an option for this encoding.

So instead, here’s an incredibly tiny self-contained addition function: add=λ32 (1 0)=b\texttt{add}=\lambda^32\ (1\ 0)=\texttt{b}

Proof

To prove: a,bN0:add ad bda+bd\forall a,b\in\mathbb{N}_0: \texttt{add}\ \langle a\rangle_d\ \langle b\rangle_d\overset{*}{\leadsto}\langle a+b\rangle_d

Proof by β\beta-reduction ({\leadsto}): add ad bd= (λ32 (1 0)) (λS(a)a) λS(b)b (λ2(λS(a)a) (1 0)) λS(b)b λ1(λS(a)a) (λS(b)b 0) λ1(λS(a)a) (λbb) λ1λaλb[a+b]=λS(a+b)[a+b]=a+bd \begin{align*} \texttt{add}\ \langle a\rangle_d\ \langle b\rangle_d=\ &(\lambda^32\ (1\ 0))\ (\lambda^{S(a)}a)\ \lambda^{S(b)}b\\ \leadsto\ &(\lambda^2(\lambda^{S(a)}a)\ (1\ 0))\ \lambda^{S(b)}b\\ \leadsto\ &\lambda^1(\lambda^{S(a)}a)\ (\lambda^{S(b)}b\ 0)\\ \leadsto\ &\lambda^1(\lambda^{S(a)}a)\ (\lambda^{b}b)\\ \leadsto\ &\lambda^1\lambda^{a}\lambda^{b}[a+b]=\lambda^{S(a+b)}[a+b]=\langle a+b\rangle_d \end{align*}

Isn’t it magical? This is one of the reasons I still like the encoding: it abuses the de Bruijn shifting so elegantly! It’s basically like a metacircular numeral system since it hijacks the host reducer’s computations!

On the other hand, I was not yet able to come up with further such elegant functions (maybe you can help?) – instead, I came up with some hybrid implementations which use multiple different numeral systems (in this case, Church’s).

A Church numeral is not that different from a de Bruijn numeral, as it turns out. It is defined like this:

nc=λ100n \begin{align*} \langle n\rangle_c &= \lambda^1\overbrace{0\circ\dots\circ0}^{\mathclap{n}} \end{align*}

Converting (“apostatizing”) a Church numeral to a de Bruijn numeral is done using the conv function:

conv=λ0 k=t k \texttt{conv}=\lambda0\ \texttt{k}=\texttt{t}\ \texttt{k}

With the internal structure of the Church encoding, this basically comes down to applying succ n1n-1

Proof

To prove: nN0:conv ncnd\forall n\in\mathbb{N}_0: \texttt{conv}\ \langle n\rangle_c\overset{*}{\leadsto}\langle n\rangle_d

Proof by beta reduction (\leadsto): conv nc= (λ0 k) λ00n (λ00n) k kkn=b k (b k ( (b k 1d))succ λS(n)n=nd \begin{align*} \texttt{conv}\ \langle n\rangle_c=\ &(\lambda0\ \texttt{k})\ \lambda\overbrace{0\circ\dots\circ0}^{\mathclap{n}}\\ \leadsto\ &(\lambda\overbrace{0\circ\dots\circ0}^{n})\ \texttt{k}\\ \leadsto\ &\overbrace{\texttt{k}\circ\dots\circ\texttt{k}}^{n}=\texttt{b}\ \texttt{k}\ (\texttt{b}\ \texttt{k}\ (\ldots\ (\texttt{b}\ \texttt{k}\ \langle1\rangle_d)\ldots)\\ \overset{\texttt{succ}}{\leadsto}\ &\lambda^{S(n)}n=\langle n\rangle_d \end{align*}

In general, assuming an encoding ee of ne\langle n\rangle_e

conv*=fix (λ3zero?* 0 1 (2 (succ 1) (pred* 0))) 0d \texttt{conv*}=\texttt{fix}\ (\lambda^3\texttt{zero?*}\ 0\ 1\ (2\ (\texttt{succ}\ 1)\ (\texttt{pred*}\ 0)))\ \langle 0\rangle_d

The function consists of fairly typical fix-recursion using a left case (zero? 0 1)(\texttt{zero?}\ 0\ 1) to return the constructed term if the input number is zero and a right case (2 (succ 1) (pred* 0))(2\ (\texttt{succ}\ 1)\ (\texttt{pred*}\ 0)) that calls the fix-induced function recursively with the incremented de Bruijn numeral and decremented input number.

  *\ *\ *

Anyway, with the insight on Church numerals, we find that the de Bruijn numerals are defined from nothing else than ad=kka.\langle a\rangle_d=\overbrace{\texttt{k}\circ\dots\circ\texttt{k}}^{\mathclap{a}}.

Indeed, any operation on Church numerals can now be made to return a de Bruijn numeral, simply by applying 1d\langle1\rangle_d

Next, an actual problem where I use de Bruijn numerals in practice all the time!

I always found Church nn-tuples really hard to work with. This is because selecting the iith element – again – requires an increasing number of abstractions! In fact their behavior is so similar that I could only now make use of them after gaining insight about de Bruijn numerals. They are defined like this: tuplen=λS(n)0 n [n1]  1\texttt{tuple}_n=\lambda^{S(n)}0\ n\ [n-1]\ \ldots\ 1

Luckily, such a term can be constructed using de Bruijn numerals. The idea is simple: First, we apply id\langle i\rangle_d

Finally, with this knowledge, Church nn-tuples can be used almost like Church lists. Some further functions: push=λ31 (0 2)=q''pop0=λ21 (λ11)pop1=λ21 (λ22 1)popn=λ31 (2 b k 0)move=λ31 (λ13 b (λ10 1) 1) \begin{align*} \texttt{push}&=\lambda^31\ (0\ 2)=\texttt{q''}\\ \texttt{pop}_0&=\lambda^21\ (\lambda^11)\\ \texttt{pop}_1&=\lambda^21\ (\lambda^22\ 1)\\ \texttt{pop}_n&=\lambda^31\ (2\ \texttt{b}\ \texttt{k}\ 0)\\ \texttt{move}&=\lambda^31\ (\lambda^13\ \texttt{b}\ (\lambda^10\ 1)\ 1) \end{align*}

where move moves the first element to the nnth position, without knowing the size of the tuple! move ic [x1,,xn][x2,,x1,xn]\texttt{move}\ \langle i\rangle_c\ [x_1,\dots,x_n]\overset{*}{\leadsto}[x_2,\dots,x_1,\dots x_n]

Notably, something like move or pop-n generally requires recursion for Church lists. At the same time for Church nn-tuples, something as simple as counting the elements is not possible for the same reasons as a zero? function for de Bruijn numerals, as the counting function would have to ignore (and count) nn applied arguments.

(see also a Forth/postscript-like stack language DSL in bruijn using this stack encoding here)

All presented definitions can be found in bruijn’s standard library: std/Number/Bruijn, std/Tuples.

Thanks for reading. Please let me know if you know of – or come up with! – other/better operations for de Bruijn numerals. Contact me via email. Support on Ko-fi. Subscribe on RSS. Follow on Mastodon.

Wadsworth, Christopher. 1980. “Some Unusual λ\lambda-Calculus Numeral Systems.” To HB Curry: Essays on Combinatory Logic, Lambda Calculus; Formalism.

联系我们 contact @ memedata.com