|
0 |
Wagon
|
|
1 |
=====
|
|
2 |
|
|
3 |
Introduction
|
|
4 |
------------
|
|
5 |
|
|
6 |
In a conventional concatenative language, every symbol represents
|
|
7 |
a function which takes program states to program states, and the program
|
|
8 |
is a concatenation (sequential composition) of such functions.
|
|
9 |
This is fine when it comes to manipulating state, but what about control?
|
|
10 |
|
|
11 |
One can allow individual functions to be defined and named seperately,
|
|
12 |
then applied, perhaps conditionally and perhaps recursively. While
|
|
13 |
this is conventional in the world of concatenative languages, such
|
|
14 |
languages are arguably no longer purely concatenative (a subject
|
|
15 |
explored by [Carriage][] and [Equipage][]).
|
|
16 |
|
|
17 |
If one wishes the language to remain purely concatenative, it seems one
|
|
18 |
must store control structures in the state (for example, [Equipage][]
|
|
19 |
stores functions on the stack) or, more drastically, allow the function
|
|
20 |
that is being constructed, to be examined somehow during the construction
|
|
21 |
process. Functions typically aren't considered examinable in this way,
|
|
22 |
but even so, examining it this way is not very different from putting
|
|
23 |
a copy of the program itself in the state.
|
|
24 |
|
|
25 |
**Wagon** is an experiment with a third option: *second-order functions*.
|
|
26 |
Instead of functions which take states to states, the symbols of the
|
|
27 |
language represent functions which take functions from states to states,
|
|
28 |
to functions that take states to states. The state is merely a stack of
|
|
29 |
integers, and the program is purely a concatenation of functions.
|
|
30 |
|
|
31 |
My hope was that these second-order functions could express control in
|
|
32 |
addition to expressing state manipulation. This does turn out to be the
|
|
33 |
case, but only to a degree. As far as I have been able to determine,
|
|
34 |
they can express some control structures, but not arbitrary ones.
|
|
35 |
|
|
36 |
Fundamentals of Wagon
|
|
37 |
---------------------
|
|
38 |
|
|
39 |
Talking about functions that themselves work on functions is admittedly somewhat
|
|
40 |
awkward, so let's define some terms. Let's call a function that takes states
|
|
41 |
to states an _operation_. Let's call a function that takes operations to
|
|
42 |
operations a _macro_. A Wagon program, then, consists of a sequence of symbols,
|
|
43 |
each of which represents a macro. These individual macros are concatenated
|
|
44 |
(sequentially composed) to form a single macro.
|
|
45 |
|
|
46 |
Since a macro takes operations to operations, it's not possible to "run" a macro
|
|
47 |
directly. So, when asked to "run" or "evaluate" a Wagon program,
|
|
48 |
we take the following convention: apply the macro that the Wagon program represents
|
|
49 |
to the identity function, and apply the resulting function to an initial stack.
|
|
50 |
We may allow a Wagon program to accept input by supplying it on the initial stack,
|
|
51 |
but usually the initial stack is just empty. The output is usually a depiction
|
|
52 |
of the final state of the stack.
|
|
53 |
|
|
54 |
Many of the primitive macros in Wagon are based directly on operations. There are
|
|
55 |
two main ways we "lift" an operation _k_ to a macro. The first is a macro which takes
|
|
56 |
an operation _o_ and returns an operation which performs _o_ then performs _k_ —
|
|
57 |
we call this sort of macro an _after-lifting_ of _k_. The second is a macro which
|
|
58 |
takes an operation _o_ and returns an operation which performs _k_ then performs _o_ —
|
|
59 |
we call this sort of macro a _before-lifting_ of _k_.
|
|
60 |
|
|
61 |
Wagon's convention is that after-lifted operations are represented by lowercase letters,
|
|
62 |
and before-lifted operations are represented by uppercase letters. Macros which do not
|
|
63 |
clearly fall into either of these two categories are represented by punctuation symbols.
|
|
64 |
|
|
65 |
Primitive Macros of Wagon
|
|
66 |
-------------------------
|
|
67 |
|
|
68 |
-> Tests for functionality "Evaluate Wagon Program"
|
|
69 |
|
|
70 |
-> Functionality "Evaluate Wagon Program" is implemented by
|
|
71 |
-> shell command "bin/wagon run %(test-body-file)"
|
|
72 |
|
|
73 |
-> Functionality "Evaluate Wagon Program" is implemented by
|
|
74 |
-> shell command "bin/wagon eval %(test-body-file)"
|
|
75 |
|
|
76 |
Note that in the following examples, the `===>` line is not part of the program;
|
|
77 |
instead it shows the expected result of running each program. In the expected
|
|
78 |
result, the resulting stack is depicted top-to-bottom.
|
|
79 |
|
|
80 |
### Rudimentary Arithmetic ###
|
|
81 |
|
|
82 |
`i` is a macro which takes an operation _o_ and returns an operation which
|
|
83 |
performs _o_ then pushes a 1 onto the stack.
|
|
84 |
|
|
85 |
`s` is a macro which takes an operation _o_ and returns an operation which
|
|
86 |
performs _o_ then pops _a_ from the stack then pops _b_ from the stack and
|
|
87 |
pushes _b_ - _a_.
|
|
88 |
|
|
89 |
With these we can construct some numbers.
|
|
90 |
|
|
91 |
i
|
|
92 |
===> [1]
|
|
93 |
|
|
94 |
iis
|
|
95 |
===> [0]
|
|
96 |
|
|
97 |
iis is
|
|
98 |
===> [-1]
|
|
99 |
|
|
100 |
i iis is s
|
|
101 |
===> [2]
|
|
102 |
|
|
103 |
As you can see, `i` and `s` are after-lifted operations. There are also
|
|
104 |
before-lifted counterparts of them:
|
|
105 |
|
|
106 |
`I` is a macro which takes an operation _o_ and returns an operation which
|
|
107 |
pushes a 1 onto the stack then performs _o_.
|
|
108 |
|
|
109 |
`S` is a macro which takes an operation _o_ and returns and operation which
|
|
110 |
pops _a_ from the stack then pops _b_ from the stack and pushes _b_ - _a_
|
|
111 |
then performs _o_.
|
|
112 |
|
|
113 |
SII
|
|
114 |
===> [0]
|
|
115 |
|
|
116 |
Note also that whitespace maps to the identity function (a macro which takes
|
|
117 |
an operation and returns that same operation) so is effectively insignificant
|
|
118 |
in a Wagon program.
|
|
119 |
|
|
120 |
### Rudimentary Stack Manipulation ###
|
|
121 |
|
|
122 |
`p` is an after-lifting of the operation: pop a value from the stack and discard it.
|
|
123 |
|
|
124 |
i iis iis iis ppp
|
|
125 |
===> [1]
|
|
126 |
|
|
127 |
`P` is the before-lifted counterpart to `p`.
|
|
128 |
|
|
129 |
PI
|
|
130 |
===> []
|
|
131 |
|
|
132 |
`d` is an after-lifting of the operation: duplicate the top value on the stack.
|
|
133 |
|
|
134 |
iis ddd
|
|
135 |
===> [0,0,0,0]
|
|
136 |
|
|
137 |
`D` is the before-lifted counterpart to `d`.
|
|
138 |
|
|
139 |
DDDI
|
|
140 |
===> [1,1,1,1]
|
|
141 |
|
|
142 |
### Sophisticated Stack Manipulation ###
|
|
143 |
|
|
144 |
`r` is an after-lifted operation: it pops a value _n_ from the stack. Then it
|
|
145 |
pops _n_ values from the stack and temporarily remembers them. Then it
|
|
146 |
reverses the remainder of the stack. Then it pushes those _n_ remembered
|
|
147 |
values back onto the stack. _n_ must be zero or one.
|
|
148 |
|
|
149 |
iis i iiisiss
|
|
150 |
===> [2,1,0]
|
|
151 |
|
|
152 |
iis i iiisiss iis r
|
|
153 |
===> [0,1,2]
|
|
154 |
|
|
155 |
iis i iiisiss i r
|
|
156 |
===> [2,0,1]
|
|
157 |
|
|
158 |
`R` is the before-lifted counterpart to `r`.
|
|
159 |
|
|
160 |
I SII
|
|
161 |
===> [1,0]
|
|
162 |
|
|
163 |
R SII I SII
|
|
164 |
===> [0,1]
|
|
165 |
|
|
166 |
### Rudimentary Control Flow ###
|
|
167 |
|
|
168 |
`@` (pronounced "while") is a macro which takes an operation _o_ and returns
|
|
169 |
an operation that repeatedly performs _o_ as long as there are elements on
|
|
170 |
the stack and the top element of the stack is non-zero.
|
|
171 |
|
|
172 |
p@ I I I SII SII
|
|
173 |
===> [0,0]
|
|
174 |
|
|
175 |
An "if" can be constructed by writing a "while" that, first thing it does is,
|
|
176 |
pop the non-zero it detected, and last thing it does is, push a 0 onto the
|
|
177 |
stack. Then immediately afterwards, pop the top element of the stack (which
|
|
178 |
we know must be zero because we know the loop just exited, whether it was
|
|
179 |
executed once, or not at all.)
|
|
180 |
|
|
181 |
Computational Class
|
|
182 |
-------------------
|
|
183 |
|
|
184 |
When Wagon was first formed, it was not clear if it would be Turing-complete
|
|
185 |
or not. The author observed it was possible to translate many programs
|
|
186 |
written in [Loose Circular Brainfuck][] into Wagon, using the following
|
|
187 |
correspondence:
|
|
188 |
|
|
189 |
+ iisiss
|
|
190 |
- is
|
|
191 |
> iisrir
|
|
192 |
< iriisr
|
|
193 |
x[y]z y@Xz
|
|
194 |
|
|
195 |
Loose Circular Brainfuck is Turing-complete, so if this correspondence
|
|
196 |
was total, Wagon would be shown Turing-complete too. However, the correspondence
|
|
197 |
is not total.
|
|
198 |
|
|
199 |
It's the `@` that's the problem. We can construct a "while" loop
|
|
200 |
with contents, and with operations that happen before it,
|
|
201 |
and with operations that happen after it. And the contents can themselves
|
|
202 |
contain a nested "while" loop. But we cannot place a second "while" loop
|
|
203 |
*after* an already-given "while" loop. That is, we cannot have more than
|
|
204 |
one "while" loop on the same nesting level.
|
|
205 |
|
|
206 |
This might be best illustrated with a depiction of the nested structure that
|
|
207 |
a Wagon program represents.
|
|
208 |
|
|
209 |
-> Tests for functionality "Depict Wagon Program"
|
|
210 |
|
|
211 |
-> Functionality "Depict Wagon Program" is implemented by
|
|
212 |
-> shell command "bin/wagon depict %(test-body-file)"
|
|
213 |
|
|
214 |
p@ I I I SII SII
|
|
215 |
===> Push1 Push1 Sub Push1 Push1 Sub Push1 Push1 Push1 (while Pop)
|
|
216 |
|
|
217 |
is@I is@I
|
|
218 |
===> Push1 (while Push1 (while Push1 Sub) Push1 Sub)
|
|
219 |
|
|
220 |
isis@I @I
|
|
221 |
===> Push1 (while Push1 (while Push1 Sub Push1 Sub))
|
|
222 |
|
|
223 |
i@Dp
|
|
224 |
===> Dup (while Push1) Pop
|
|
225 |
|
|
226 |
i@Dp i@Dp
|
|
227 |
===> Dup (while Dup (while Push1) Pop Push1) Pop
|
|
228 |
|
|
229 |
While it is possible to simulate a universal Turing machine with only a
|
|
230 |
single top-level "while" loop and a series of "if" statements inside the
|
|
231 |
body of the "while" (see, for example, [Burro][]), it is not known to me
|
|
232 |
if it is possible to simulate a universal Turing machine with only
|
|
233 |
strictly-singly-nested "while" loops as constructible in Wagon.
|
|
234 |
|
|
235 |
Any inner "while" loop can be turned into an "if" as described above,
|
|
236 |
but a conventional "if/else" is not possible, because it would normally
|
|
237 |
require two consecutive "while"s, one to check the condition, and one
|
|
238 |
to check the inverse of the condition. Similarly, it would not be
|
|
239 |
possible to check if the finite control of a Turing machine is in one
|
|
240 |
state, or another state. This would seem to be a fairly serious restriction.
|
|
241 |
|
|
242 |
However, shortly after being announced in the `#esoteric` IRC channel, it was
|
|
243 |
[shown by int-e](https://gist.github.com/int-e/e4ae1f40f8173d67860d8f8e45c433c0)
|
|
244 |
that it is possible to compile a Tag system into a Wagon program.
|
|
245 |
Since Tag systems are Turing-complete, Wagon is as well.
|
|
246 |
|
|
247 |
As of this writing, it remains unclear if Wagon is able to simulate a
|
|
248 |
Turing machine or Loose Circular Brainfuck program directly rather than
|
|
249 |
via a Tag system.
|
|
250 |
|
|
251 |
[Loose Circular Brainfuck]: https://esolangs.org/wiki/Loose_Circular_Brainfuck_(LCBF)
|
|
252 |
[Carriage]: https://catseye.tc/node/Carriage
|
|
253 |
[Equipage]: https://catseye.tc/node/Equipage
|
|
254 |
[Burro]: https://catseye.tc/node/Burro
|