-
Notifications
You must be signed in to change notification settings - Fork 0
/
nested-modal-transducers.scm
358 lines (320 loc) · 10.5 KB
/
nested-modal-transducers.scm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
;
; Runnable example code, in R5RS Scheme, to accompany the article
; about Nested Modal Transducer Assemblages.
;
; Example usage: install Chicken Scheme, then run
; csi -q -b nested-modal-transducers.scm
; to run all the tests. All tests passed if the output is only `()`'s.
;
; All of this code is in the public domain. Do what you like with it.
;
(define expect
(lambda (pairs)
(if (null? pairs)
'()
(let* ((pair (car pairs))
(fst (car pair))
(snd (cdr pair)))
(if (equal? fst snd)
(expect (cdr pairs))
pair)))))
;
; Purely functional definition of a simple transducer.
;
(define light-transducer
(lambda (mode input)
(let* ((transition (list mode input)))
(cond
((equal? transition '(on turn-off))
(list 'off '()))
((equal? transition '(off turn-on))
(list 'on '(ring-bell)))
(else
(list mode '()))))))
;
; Purely functional test harness for transducers:
; Determine what state and outputs it will produce, given a sequence of inputs.
; You can think of it as having a type like:
;
; rehearse :: Transducer -> State -> [Input] -> (State, [Output])
;
(define rehearse
(lambda (t state inputs)
(if (null? inputs)
(list state '())
(let* ((input (car inputs))
(result1 (t state input))
(state1 (car result1))
(outputs1 (cadr result1))
(result2 (rehearse t (car result1) (cdr inputs)))
(state2 (car result2))
(outputs2 (cadr result2)))
(list state2 (append outputs1 outputs2))))))
(display (expect (list
(cons
(rehearse light-transducer 'on '(turn-off))
'(off ())
)
(cons
(rehearse light-transducer 'off '(turn-off))
'(off ())
)
(cons
(rehearse light-transducer 'off '(turn-on turn-on turn-off))
'(off (ring-bell))
)
(cons
(rehearse light-transducer 'on '(turn-on turn-on turn-off))
'(off ())
)
)))
(newline)
;
; ---- ---- ---- ----
;
(define combine-transducers
(lambda (ta tb)
(lambda (state input)
(let* ((state-a (car state))
(result-a (ta state-a input))
(newstate-a (car result-a))
(outputs-a (cadr result-a))
(state-b (cdr state))
(result-b (tb state-b input))
(newstate-b (car result-b))
(outputs-b (cadr result-b)))
(list (cons newstate-a newstate-b) (append outputs-a outputs-b))))))
(define two-light-transducer (combine-transducers light-transducer light-transducer))
(display (expect (list
(cons
(rehearse two-light-transducer '(on . off) '(turn-off))
'((off . off) ())
)
(cons
(rehearse two-light-transducer '(on . off) '(turn-off turn-on))
'((on . on) (ring-bell ring-bell))
)
)))
(newline)
;
; ---- ---- ---- ----
;
(define counting-light-transducer
(lambda (config input)
(let* ((mode (car config))
(count (cadr config))
(transition (list mode input)))
(cond
((equal? transition '(on turn-off))
(list (list 'off count) '()))
((equal? transition '(off turn-on))
(list (list 'on (+ count 1)) '(ring-bell)))
(else
(list config '()))))))
(display (expect (list
(cons
(rehearse counting-light-transducer '(off 0) '(turn-on))
'((on 1) (ring-bell))
)
(cons
(rehearse counting-light-transducer '(off 0) '(turn-on turn-on))
'((on 1) (ring-bell))
)
(cons
(rehearse counting-light-transducer '(off 0) '(turn-on turn-on turn-off))
'((off 1) (ring-bell))
)
(cons
(rehearse counting-light-transducer '(off 0) '(turn-on turn-on turn-off turn-on))
'((on 2) (ring-bell ring-bell))
)
)))
(newline)
;
; Nested state machine. The light is now in a room, behind a door.
; It can only be turned on or off when the door is open.
;
(define door-transducer
(lambda (config input)
(let* ((mode (car config))
(light-config (cadr config))
(transition (list mode input)))
(cond
((equal? transition '(closed open))
(list (list 'opened light-config) '()))
((equal? transition '(opened close))
(list (list 'closed light-config) '()))
((equal? mode 'opened)
(let* ((inner-result (counting-light-transducer light-config input))
(new-light-config (car inner-result))
(light-outputs (cadr inner-result)))
(list (list mode new-light-config) light-outputs)))
(else
(list config '()))))))
(display (expect (list
(cons
(rehearse door-transducer '(closed (off 0)) '(open))
'((opened (off 0)) ())
)
(cons
(rehearse door-transducer '(closed (off 0)) '(turn-on))
'((closed (off 0)) ())
)
(cons
(rehearse door-transducer '(closed (off 0)) '(open turn-on close))
'((closed (on 1)) (ring-bell))
)
)))
(newline)
;
; Array of orthogonal regions - a list of lights are behind a barn door.
;
(define transduce-all
(lambda (t input configs acc)
(if (null? configs)
(list (reverse (car acc)) (cadr acc))
(let* ((config (car configs))
(rest-configs (cdr configs))
(acc-configs (car acc))
(acc-outputs (cadr acc))
(result (t config input))
(new-config (car result))
(these-outputs (cadr result))
(new-acc (list (cons new-config acc-configs) (append these-outputs acc-outputs))))
(transduce-all t input rest-configs new-acc)))))
(define barn-transducer
(lambda (config input)
(let* ((mode (car config))
(light-configs (cadr config))
(transition (list mode input)))
(cond
((equal? transition '(closed open))
(list (list 'opened light-configs) '()))
((equal? transition '(opened close))
(list (list 'closed light-configs) '()))
((equal? mode 'opened)
(let* ((inner-results (transduce-all counting-light-transducer input light-configs '(() ())))
(new-light-configs (car inner-results))
(light-outputs (cadr inner-results)))
(list (list mode new-light-configs) light-outputs)))
(else
(list config '()))))))
(display (expect (list
(cons
(rehearse barn-transducer '(closed ((off 0) (on 0)) ) '(open))
'( (opened ((off 0) (on 0)) ) ())
)
(cons
(rehearse barn-transducer '(closed ((off 0) (on 0)) ) '(turn-on))
'( (closed ((off 0) (on 0)) ) ())
)
(cons
(rehearse barn-transducer '(closed ((off 0) (on 0)) ) '(open turn-on close))
'( (closed ((on 1) (on 0)) ) (ring-bell))
)
(cons
(rehearse barn-transducer '(closed ((off 0) (off 0)) ) '(open turn-on close))
'( (closed ((on 1) (on 1)) ) (ring-bell ring-bell))
)
)))
(newline)
;
; Entry and exit actions
;
; Like the article says, we don't pretend to have a good solution, we only
; want to show that it is possible.
;
; door-transducer-2 is the same as door-transducer except that the
; counting-light-transducer nested within it, is decorated with
; add-entry-exit-outputs.
;
(define add-entry-exit-outputs
(lambda (t config input)
(let* ((old-mode (car config))
(result (t config input))
(new-config (car result))
(new-mode (car new-config))
(new-data (cadr new-config))
(outputs (cadr result))
(exit-outputs (if (equal? old-mode 'off) '(buzz-buzzer) '()))
(entry-outputs (if (equal? new-mode 'closed) '(blow-horn) '()))
(new-outputs (append exit-outputs outputs entry-outputs)))
(list new-config new-outputs))))
(define door-transducer-2
(lambda (config input)
(let* ((mode (car config))
(light-config (cadr config))
(transition (list mode input)))
(cond
((equal? transition '(closed open))
(list (list 'opened light-config) '()))
((equal? transition '(opened close))
(list (list 'closed light-config) '()))
((equal? mode 'opened)
(let* ((inner-result (add-entry-exit-outputs counting-light-transducer light-config input))
(new-light-config (car inner-result))
(light-outputs (cadr inner-result)))
(list (list mode new-light-config) light-outputs)))
(else
(list config '()))))))
(define deco-door-transducer
(lambda (config input)
(add-entry-exit-outputs door-transducer-2 config input)))
(display (expect (list
(cons
(rehearse deco-door-transducer '(closed (off 0)) '(open))
'((opened (off 0)) ())
)
(cons
(rehearse deco-door-transducer '(closed (off 0)) '(turn-on))
'((closed (off 0)) (blow-horn))
)
(cons
(rehearse deco-door-transducer '(closed (off 0)) '(open turn-on close))
'((closed (on 1)) (buzz-buzzer ring-bell blow-horn))
)
)))
(newline)
;
; Synthesized events
;
(define make-gui-input-synthesizing-transducer
(lambda (t)
(lambda (config input)
(let* ((mode (car config)))
(cond
((and (equal? mode 'mouse-down) (list? input) (equal? (car input) 'mouse-move))
(t config (list 'drag (cadr input) (caddr input))))
(else
(t config input)))))))
(define base-gui-transducer
(lambda (config input)
(let* ((mode (car config))
(x (cadr config))
(y (caddr config))
(transition (list mode input)))
(cond
((equal? transition '(mouse-down mouse-release))
(list (list 'mouse-up x y) '()))
((equal? transition '(mouse-up mouse-press))
(list (list 'mouse-down x y) (list (list 'show-click x y))))
((equal? (car input) 'mouse-move)
(let* ((new-x (cadr input)) (new-y (caddr input)))
(list (list mode new-x new-y) '())))
((equal? (car input) 'drag)
(let* ((new-x (cadr input)) (new-y (caddr input)))
(list (list mode new-x new-y) (list (list 'show-hand new-x new-y)))))
(else
(list (list mode x y) '()))))))
(define gui-transducer (make-gui-input-synthesizing-transducer base-gui-transducer))
(display (expect (list
(cons
(rehearse gui-transducer '(mouse-up 0 0) '((mouse-move 10 10) mouse-press mouse-release))
'((mouse-up 10 10) ((show-click 10 10)))
)
(cons
(rehearse gui-transducer '(mouse-up 0 0) '(mouse-press (mouse-move 10 10) mouse-release))
'((mouse-up 10 10) ((show-click 0 0) (show-hand 10 10)))
)
)))
(newline)