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
|
@node Signals
@section Signals
Game state is a function of time. The player's score, the current
stage, an enemy's hit points, etc. all change in response to events
that happen at discrete points in time. Typically, this means that a
number of callback procedures are registered to respond to events
which mutate the relevant data structures. However, this approach,
while simple and effective, comes at the price of readability,
reproducibility, and expression. Instead of explicitly mutating data
and entering ``callback hell,'' Sly abstracts and formalizes the
process using a functional reactive programming style.
In Sly, time-varying values are called ``signals'', and they are
defined in a declarative and functional manner. Rather than
describing the process of mutation procedurally, one describes the
relationships between signals instead. The result is a ``signal
graph'', a directed acyclic graph of event responses.
@example
(define-signal position
(signal-fold v+ (vector2 320 240)
(signal-map (lambda (v) (v* v 4))
(signal-sample 1 key-arrows))))
@end example
This signal describes a relationship between the arrow keys on the
keyboard and the position of the player. @code{signal-sample} is used
to trigger a signal update upon every game tick that provides the
current state of the arrow keys. @code{key-arrows} is a 2D vector
that maps to the current state of the arrow keys, allowing for 8
directional movement. This vector is then scaled 4x to make the
player move faster. Finally, the scaled vector is added to the
previous player position via @code{signal-fold}. The player's
position is at (320, 240) initially. As you can see, there are no
callbacks and explicit mutation needed, and the position seems to
magically change with the passage of time and pushing of the arrow
keys.
@deffn {Scheme Procedure} signal? @var{obj}
Return @code{#t} if @var{obj} is a signal.
@end deffn
@deffn {Scheme Procedure} make-signal @var{value}
Wrap @var{value} in a signal.
@end deffn
@deffn {Scheme Syntax} define-signal @var{name} @var{value}
Create a top-level signal variable called @var{name}. If the variable
already exists and refers to a signal then its outputs will be spliced
into the new signal. If the given value is not a signal then it will
be put into one via @code{make-signal}.
@code{define-signal} is particularly useful when working at the REPL.
A top-level signal variable defined by @code{define-signal} can be
redefined at runtime, and the signals that depended on the old signal
will continue to work with the new signal.
@end deffn
@deffn {Scheme Procedure} signal-ref @var{signal}
Return the value stored within @var{signal}.
@end deffn
@deffn {Scheme Procedure} signal-ref-maybe object
Return the value stored within @var{object} if @var{object} is a
signal. Otherwise, return @var{object}.
@end deffn
@deffn {Scheme Syntax} signal-let ((@var{var} @var{signal}) @dots{}) @var{body} @dots{}
Evaluate @var{body} in the context of the local bindings defined by
the two-element lists @code{((var signal) @dots{})}.
@code{signal-let} works like regular @code{let}, except that it
derefences @var{signal} before binding to @var{var}.
@end deffn
@deffn {Scheme Syntax} signal-let* ((@var{var} @var{signal}) @dots{}) @var{body} @dots{}
Similar to @code{signal-let}, but the variable bindings are performed
sequentially. This means that all initialization expressions are
allowed to use the variables defined to the their left in the binding
list.
@end deffn
@deffn {Scheme Procedure} signal-set! signal-box value
Change the contents of @var{signal} to @var{value}. This procedure
should almost never be used, except to bootstrap a root node of a
signal graph.
@end deffn
@deffn {Scheme Procedure} hook->signal @var{hook} @var{init} @var{proc}
Create a new signal whose initial value is @var{init} and whose future
values are calculated by applying @var{proc} to the arguments passed
when @var{hook} is run.
@end deffn
@deffn {Scheme Procedure} signal-merge @var{signal1} @var{signal2} . @var{rest}
Create a new signal whose value is the that of the most recently
updated signal in @var{signal1}, @var{signal2}, etc. The initial
value is that of @var{signal1}.
@end deffn
@deffn {Scheme Procedure} signal-zip . @var{signals}
Create a new signal whose value is a list of the values stored in
@var{signals}.
@end deffn
@deffn {Scheme Procedure} signal-map @var{proc} @var{signal} . @var{rest}
Create a new signal that applies @var{proc} to the values of
@var{SIGNAL}. More than one input signal may be specified, in which
case @var{proc} must accept as many arguments as there are input
signals.
@end deffn
@deffn {Scheme Procedure} signal-sample-on @var{value-signal} @var{sample-signal}
Create a new signal that takes on the value of @var{value-signal}
whenever @var{sample-signal} receives a new value.
@end deffn
@deffn {Scheme Procedure} signal-negate @var{signal}
Create a new signal whose value is the negation of @var{signal} by
applying @code{not} to each value received.
@end deffn
@deffn {Scheme Procedure} signal-fold @var{proc} @var{init} @var{signal} . @var{rest}
Create a new signal that applies @var{proc} with the value received
from @var{signal} and the past value of itself, starting with
@var{init}. Like @code{signal-map}, more than one input signal may be
given.
@end deffn
@deffn {Scheme Procedure} signal-filter @var{predicate} @var{default} @var{signal}
Create a new signal that takes on the value received from @var{signal}
when it satisfies the procedure @var{predicate}. The value of the
signal is @var{default} in the case that the predicate is never
satisfied.
@end deffn
@deffn {Scheme Procedure} signal-drop @var{predicate} @var{default} @var{signal}
Create a new signal that takes on the value received from @var{signal}
when it does @emph{not} satisfy the procedure @var{predicate}. The
value of the signal is @var{default} in the case that the predicate is
never satisfied.
@end deffn
@deffn {Scheme Procedure} signal-drop-repeats @var{signal} [@var{equal?}]
Create a new signal that drops the value received from @var{signal}
when it is equivalent to the current value. By default, @code{equal?}
is used for testing equivalence.
@end deffn
@deffn {Scheme Procedure} signal-switch @var{predicate} @var{on} @var{off}
Create a new signal whose value is that of the signal @var{on} when
the signal @var{predicate} is true, or the value of the signal
@var{off} otherwise.
@end deffn
@deffn {Scheme Procedure} signal-constant @var{constant} @var{signal}
Create a new signal whose value is always @var{constant} no matter the
value received from @var{signal}.
@end deffn
@deffn {Scheme Procedure} signal-count @var{signal} [@var{start}] [@var{step}]
Create a new signal that increments a counter by @var{step} when a
value from @var{signal} is received, starting from @var{start}. By
default, @var{start} is 0 and @var{step} is 1.
@end deffn
@deffn {Scheme Procedure} signal-tap @var{proc} @var{signal}
Create a new signal that applies @var{proc} for side-effects when a
value from @var{signal} is received. The value of the new signal will
always be the value of @var{signal}. This signal is a convenient way
to sneak in a procedure that with a side-effect into a signal graph.
Such a signal might write text to a file, or play a sound.
@end deffn
@deffn {Scheme Procedure} signal-timestamp @var{signal}
Create a new signal whose value is a pair, the car of which is the
time that the value of @var{signal} was received and the cdr of which
is the received value.
@end deffn
@deffn {Scheme Procedure} signal-time @var{signal}
Create a new signal whose value is the time that the value of
@var{signal} was received.
@end deffn
@deffn {Scheme Procedure} signal-sample @var{step} @var{signal}
Create a new signal that takes on the value of @var{signal} every
@var{step} ticks.
@end deffn
@deffn {Scheme Procedure} signal-every @var{step}
Create a new signal that emits @var{step} every @var{step} ticks.
@end deffn
@deffn {Scheme Procedure} signal-timer [@var{step}]
Create a new signal that emits the total time elapsed since its
creation every @var{step} (1 by default) ticks.
@end deffn
@deffn {Scheme Procedure} signal-since @var{step} @var{signal}
Create a new signal that emits the time since @var{signal} was updated
ever @var{step} ticks.
@end deffn
@deffn {Scheme Procedure} signal-delay @var{delay} @var{signal}
Create a new signal that delays propagation of @var{signal} by
@var{delay} ticks..
@end deffn
@deffn {Scheme Procedure} signal-throttle delay signal
Create a new signal that propagates @var{signal} at most once every
@var{delay} ticks.
@end deffn
@deffn {Scheme Syntax} signal-generator @var{body} @dots{}
Create a new signal whose value is the most recently yielded value of
the coroutine defined by @var{body}. A special @code{yield} syntax is
available within @var{body} to specify which values are passed to the
signal.
@end deffn
@deffn {Scheme Procedure} signal-call @var{proc-signal} . @var{arg-signals}
Create a new signal that applies the procedure within
@var{proc-signal} to the arguments in @var{arg-signals}.
@end deffn
|