A
tag system
is a model
of computation that works like a simplified Turing machine: instead of moving a
head back and forth over a tape, a tag system operates on a string by repeatedly adding new
characters to the end of the string and removing them from the beginning. In some ways, a tag
system’s string is like a Turing machine’s tape, but the tag system is constrained to only
operate on the edges of the string, and it only ever “moves” in one direction—toward the
end.
A tag system’s description has two parts: first, a collection of
rules, where each rule specifies some characters to append to the string
when a particular character appears at the beginning—“when the charactera
is at the beginning of the string,
append the charactersbcd
,” for
instance; and second, a number, called the
deletion
number
, which specifies how many characters to delete from the
beginning of the string after a rule has been followed.
Here’s an example tag system:
When the string begins witha
, append the
charactersbc
.
When the string begins withb
, append the
characterscaad
.
When the string begins withc
, append the
charactersccd
.
After following any of the above rules, delete three characters from the beginning of
the string—in other words, the deletion number is 3.
We can perform a tag system computation by repeatedly following rules and deleting
characters until the first character of the string has no applicable rule, or until the length
of the string is less than the deletion number.
[
58
]
Let’s try running the example tag system with the initial string'aaaaaa'
:
Current string | Applicable rule |
aaaaaa | When the string begins witha , append thecharacters bc . |
aaa bc | When the string begins witha , append thecharacters bc . |
bc bc | When the string begins withb , append thecharacters caad . |
c caad | When the string begins withc , append thecharacters ccd . |
ad ccd | When the string begins witha , append thecharacters bc . |
cd bc | When the string begins withc , append thecharacters ccd . |
c ccd | When the string begins withc , append thecharacters ccd . |
d ccd | — |
Tag systems only operate directly on strings, but we can get them to
perform sophisticated operations on other kinds of values, like numbers,
as long as we have a suitable way to encode those values as strings. One
possible way of encoding numbers is this: represent the numbern
as the stringaa
followed byn
repetitions of the stringbb
; for example, the number 3 is represented as
the stringaabbbbbb
.
Some aspects of this representation might seem redundant—we could
just represent 3 as the stringaaa
—but using pairs of characters, and having
an explicit marker at the beginning of the string, will be useful
shortly.
Once we’ve chosen an encoding scheme for numbers, we can design tag
systems that perform operations on numbers by manipulating their string
representations. Here’s a system that doubles its input number:
When the string begins witha
, append the
charactersaa
.
When the string begins withb
, append the
charactersbbbb
.
After following a rule, delete two characters from the beginning of the string (the
deletion number is 2).
Watch how this tag system behaves when started with the stringaabbbb
, representing the number
2:
aabbbb → bbbbaa
→ bbaabbbb
→ aabbbbbbbb
(representing the number 4)
→ bbbbbbbbaa
→ bbbbbbaabbbb
→ bbbbaabbbbbbbb
→ bbaabbbbbbbbbbbb
→ aabbbbbbbbbbbbbbbb
(the number 8)
→ bbbbbbbbbbbbbbbbaa
→ bbbbbbbbbbbbbbaabbbb
⋮
The doubling is clearly happening, but this tag system runs
forever—doubling the number represented by its current string, then
doubling it again, then again—which isn’t really what we had in mind. To
design a system that doubles a number just once and then halts, we need to
use different characters to encode the result so that it doesn’t trigger
another round of doubling. We can do this by relaxing our encoding scheme
to allowc
andd
characters in place ofa
andb
, and
then modifying the rules to appendcc
anddddd
instead ofaa
andbbbb
when creating the representation of the doubled number.
With those changes, the computation looks like this:
aabbbb → bbbbcc
→ bbccdddd
→ ccdddddddd
(the number 4, encoded withc
andd
instead ofa
andb
)
The modified system stops when it reachesccdddddddd
,
because there’s no rule for strings beginning withc
.
In this case, we’re only depending on the characterc
to stop the computation at the right point, so we could have safely reusedb
in the encoding of the result instead of replacing it withd
, but there’s no harm in using more characters than
are strictly needed.
It’s generally clearer to use different sets of characters to
encode input and output values rather than allowing them to overlap; as
we’ll see shortly, this also makes it easier to combine several small
tag systems into a larger one, by arranging for the output encoding of
one system to match up with the input encoding of another.
To simulate a tag system in Ruby, we need an implementation of an
individual rule (TagRule
), a collection
of rules (TagRulebook
), and the tag
system itself (TagSystem
):
class
TagRule
<
Struct
.
new
(
:first_character
,
:append_characters
)
def
applies_to?
(
string
)
string
.
chars
.
first
==
first_character
end
def
follow
(
string
)
string
+
append_characters
end
end
class
TagRulebook
<
Struct
.
new
(
:deletion_number
,
:rules
)
def
next_string
(
string
)
rule_for
(
string
)
.
follow
(
string
)
.
slice
(
deletion_number
.
.
-
1
)
end
def
rule_for
(
string
)
rules
.
detect
{
|
r
|
r
.
applies_to?
(
string
)
}
end
end
class
TagSystem
<
Struct
.
new
(
:current_string
,
:rulebook
)
def
step
self
.
current_string
=
rulebook
.
next_string
(
current_string
)
end
end
This implementation allows us to step through a tag system
computation one rule at a time. Let’s try that for the original doubling
example, this time getting it to double the number 3 (aabbbbbb
):
>>
rulebook
=
TagRulebook
.
new
(
2
,
[
TagRule
.
new
(
'a'
,
'aa'
),
TagRule
.
new
(
'b'
,
'bbbb'
)
]
)
=> #
>>
system
=
TagSystem
.
new
(
'aabbbbbb'
,
rulebook
)
=> #
>>
4
.
times
do
puts
system
.
current_string
system
.
step
end
;
puts
system
.
current_string
aabbbbbb
bbbbbbaa
bbbbaabbbb
bbaabbbbbbbb
aabbbbbbbbbbbb
=> nil
Because this tag system runs forever, we have to know in advance how many steps to execute
before the result appears—four steps, in this case—but if we used the modified version that
encodes its result withc
andd
, we could just let it run until it stops automatically. Let’s add the code to
support that:
class
TagRulebook
def
applies_to?
(
string
)
!
rule_for
(
string
)
.
nil?
&&
string
.
length
>=
deletion_number
end
end
class
TagSystem
def
run
while
rulebook
.
applies_to?
(
current_string
)
puts
current_string
step
end
puts
current_string
end
end
Now we can just callTagSystem#run
on the halting version of the tag
system and let it naturally stop at the right point:
>>
rulebook
=
TagRulebook
.
new
(
2
,
[
TagRule
.
new
(
'a'
,
'cc'
),
TagRule
.
new
(
'b'
,
'dddd'
)
]
)
=> #
>>
system
=
TagSystem
.
new
(
'aabbbbbb'
,
rulebook
)
=> #
>>
system
.
run
aabbbbbb
bbbbbbcc
bbbbccdddd
bbccdddddddd
ccdddddddddddd
=> nil