Skip to content

Commit 989dfdc

Browse files
committed
Add chapter on turing completeness
1 parent fa360da commit 989dfdc

File tree

2 files changed

+205
-0
lines changed

2 files changed

+205
-0
lines changed

src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
- [Debugging](./decl-macros/minutiae/debugging.md)
2121
- [Scoping](./decl-macros/minutiae/scoping.md)
2222
- [Import and Export](./decl-macros/minutiae/import-export.md)
23+
- [Turing Completeness](./decl-macros/minutiae/turing-completeness.md)
2324
- [Patterns](./decl-macros/patterns.md)
2425
- [Callbacks](./decl-macros/patterns/callbacks.md)
2526
- [Incremental TT Munchers](./decl-macros/patterns/tt-muncher.md)
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
# Turing Completeness
2+
3+
Rust's Declarative Macros are Turing-Complete in their expansion. Here we will prove this fact.
4+
5+
## Tag Systems
6+
7+
A [Tag System](https://en.wikipedia.org/wiki/Tag_system) is a model of computation that we will use in our proof.
8+
9+
### Definition
10+
11+
> **Note:** the definition below is taken verbatim from Wikipedia.
12+
13+
Briefly, a Tag System is a triplet \\( (m, A, P) \\) where:
14+
\\( \\)
15+
* \\( m \\) is a positive integer, called the deletion number.
16+
* \\( A \\) is a finite alphabet of symbols, one of which can be a special *halting symbol* \\( H \\). All finite (possibly empty) strings on \\( A \\) are called **words**.
17+
* \\( P \\) is a set of production rules, assigning a word \\( P(x) \\) (called a production) to each symbol \\( x \\) in \\( A \\). The production (say \\( P(H) \\)) assigned to the halting symbol is seen below to play no role in computations, but for convenience is taken to be \\( P(H) = H \\).
18+
19+
A **halting word** is a word that either:
20+
* Begins with the halting symbol \\( H \\),
21+
* or whose length is less than \\( m \\).
22+
23+
A **transformation** \\( t \\) (called the tag operation) is defined on the set of non-halting words, such that if \\( x \\) denotes the leftmost symbol of a word \\( S \\), then \\( t(S) \\) is the result of deleting the leftmost \\( m \\) symbols of \\( S \\) and appending the word \\( P(x) \\) on the right. Thus, the system processes the m-symbol head into a tail of variable length, but the generated tail depends solely on the first symbol of the head.
24+
25+
A **computation** by a tag system is a finite sequence of words produced by iterating the transformation \\( t \\), starting with an initially given word and halting when a halting word is produced.
26+
27+
### Computational Power
28+
29+
When its parameter \\( m \\) is greater than 1, a Tag System is capable of representing an arbitrary Turing Machine. We will use this fact in our proof.
30+
31+
## Proof
32+
33+
The proof consists of the following steps:
34+
35+
1. We will take an **arbitrary** Tag System \\( T \\) of \\( m > 1 \\).
36+
2. We will construct a `macro_rules!` definition `my_tag_system` that simulates this Tag System, such that:
37+
1. Every halting string in the Tag System halts in our macro.
38+
2. Every production rule in the Tag System is reproduced exactly in our macro.
39+
40+
This will prove that we can fully simulate any Tag System of \\( m > 1 \\) with a `macro_rules!` definition's expansion. And, since simulating a Tag System inherits its halting properties, our macro's expansion will halt **if and only if the Tag System halts**.
41+
42+
### An Arbitrary Tag System
43+
44+
Let's specify an arbitrary Tag System \\( T = (m, A, P) \\):
45+
46+
* \\( m \in \mathbb{N}, m > 1 \\)
47+
* \\( A \\) is our set of symbols.
48+
* \\( A = \\{ a, b, c, \ldots \\} \\)
49+
* \\( A \\) is finite
50+
* The empty string is considered a string on \\( A \\).
51+
* \\( A \\) may contain a special character for halting: \\( H \\).
52+
* \\( P \\) is a set of production rules \\( \\{ p_a, p_b, p_c, \ldots \\} \\), which can be equivalently seen as a function from \\( A \\) to the strings on \\( A \\):
53+
54+
\\[ P: A \to A^\star \\]
55+
56+
### Constructing a Macro Definition for our Tag System
57+
58+
We will now construct a `macro_rules!` definition that fully simulates `T`.
59+
60+
#### Declaration
61+
62+
We begin with the declaration. Nothing special here.
63+
64+
```rust,ignore
65+
macro_rules! my_tag_system {
66+
```
67+
68+
#### Halting Input
69+
70+
First we define what inputs will halt. There are two cases.
71+
72+
A string beginning with the special character \\( H \\) will halt.
73+
74+
```rust,ignore
75+
(H $($tail:tt)*) => {};
76+
```
77+
78+
A string of length \\( [0 \ldots (m - 1)] \\) will halt.
79+
80+
```rust,ignore
81+
() => {};
82+
83+
($ident_1:ident) => {};
84+
85+
($ident_1:ident $ident_2:ident) => {};
86+
87+
/* ... */
88+
89+
($ident_1:ident $ident_2:ident /* ... */ $ident_m_minus_2:ident) => {};
90+
91+
($ident_1:ident $ident_2:ident /* ... */ $ident_m_minus_2:ident $ident_m_minus_1:ident) => {};
92+
```
93+
94+
#### Non-Halting Input
95+
96+
> **Note**: given two strings \\( a, b \\), the concatenation of both is written as \\( a | b \\)
97+
98+
For each symbol in \\( A \\), we have a production rule. To recap:
99+
100+
A string \\( S = x | head | tail \\) with \\( \texttt{len}(head) = m - 1 \\) will be transformed into \\( t(S) \\) in the following way:
101+
102+
\\[ t(S) \\]
103+
104+
\\[ = t(x | head | tail) \\]
105+
106+
\\[ = tail | P(x) \\]
107+
108+
Thus, we write each of the rules in `P` like this:
109+
110+
```rust,ignore
111+
// P(x)
112+
(x $d_1:tt $d_2:tt /* ... */ $d_m_minus_1:tt $($tail:tt)*) => {
113+
my_tag_system!($($tail)* p_x)
114+
};
115+
```
116+
117+
Where we replace `p_x` with the expansion of \\( P(x) \\).
118+
119+
#### All Together
120+
121+
Putting it all together, it looks like this:
122+
123+
```rust
124+
macro_rules! my_tag_system {
125+
126+
// Halting Input
127+
(H $($tail:tt)*) => {};
128+
129+
() => {};
130+
131+
($ident_1:ident) => {};
132+
133+
($ident_1:ident $ident_2:ident) => {};
134+
135+
/* ... */
136+
137+
($ident_1:ident $ident_2:ident /* ... */ $ident_m_minus_2:ident) => {};
138+
139+
($ident_1:ident $ident_2:ident /* ... */ $ident_m_minus_2:ident $ident_m_minus_1:ident) => {};
140+
141+
// Non-Halting Input
142+
143+
// P(a)
144+
(a $d_1:tt $d_2:tt /* ... */ $d_m_minus_1:tt $($tail:tt)*) => {
145+
my_tag_system!($($tail)* p_a)
146+
};
147+
148+
// P(b)
149+
(b $d_1:tt $d_2:tt /* ... */ $d_m_minus_1:tt $($tail:tt)*) => {
150+
my_tag_system!($($tail)* p_b)
151+
};
152+
153+
// ...
154+
}
155+
#
156+
# fn main() {}
157+
```
158+
159+
### Analysis of our Macro Definition
160+
161+
When does our macro's expansion halt?
162+
163+
Well, as we can see from our definition:
164+
* We can represent all strings on \\( A \\).
165+
* Our macro's definition halts with all strings for which \\( T \\) halts.
166+
* Our macro's definition reproduces all production rules present in \\( T \\). No more, and no less.
167+
168+
Therefore, the macro expansion process will quite literally simulate the Tag System we've encoded in our definition.
169+
170+
Our macro's expansion will halt **if and only if \\( T \\) halts**.
171+
172+
#### Caveat: Recursion Limit
173+
174+
The Rust Compiler has, very reasonably, a `recursion_limit` parameter for macro expansion. This allows the compiler to bail itself out in case a macro expansion is taking an unreasonably long time.
175+
176+
Such a parameter does technically mean that our macros will always halt. But for the purposes of formal analysis, that's just an implementation detail.
177+
178+
> **Note**: it's the same as noting that a computer's RAM is finite, and thus doesn't represent a real Turing Machine.
179+
180+
Besides, this limit can be bypassed by setting the `recursion_limit` attribute to a very large number, for example: `#![recursion_limit = "999999999999999999"]`
181+
182+
### Relation to Turing-Completeness
183+
184+
* A Tag System with \\( m > 1 \\) is Turing-Complete.
185+
* This means that we can take an arbitrary Turing Machine \\( M \\) and convert it into a Tag System \\( T \\) with \\( m > 1 \\) that halts if and only if \\( M \\) halts.
186+
* We have just proven that we can take an arbitrary Tag System \\( T \\) and convert it into a `macro_rules!` definition `my_tag_system` that halts in its expansion if and only if \\( T \\) halts.
187+
188+
Therefore, we are able to:
189+
1. Take an arbitrary Turing Machine \\( M \\),
190+
2. Convert it into a Tag System \\( T \\) that halts if and only if \\( M \\) halts,
191+
3. And then convert it into a `macro_rules!` definition `my_tag_system` that halts in its expansion if and only if \\( T \\) halts.
192+
193+
And since the "if and only if" relation is transitive, we've just proven that `macro_rules!` expansion is Turing-Complete.
194+
195+
## Consequences of Turing-Completeness
196+
197+
The fact that Rust's Declarative Macros are Turing Complete imposes very important consequences on tooling:
198+
199+
* Will a macro always finish expanding? Undecidable.
200+
* By [Rice's Theorem](https://en.wikipedia.org/wiki/Rice%27s_theorem), most properties of a Turing-Complete system are Undecidable.
201+
* Are two macros equivalent? Who knows! It's actually very likely that this is impossible to tell in general.
202+
* Is a macro's definition arm redundant? Might be impossible to tell in general.
203+
204+
All of this is to say: as far as the author of this fragment (Félix Fischer) is concerned, it's **To Be Determined** whether or not any of these questions and many more like them are even answerable.

0 commit comments

Comments
 (0)