This is to track a feature that’s stubbed out but not implemented yet, quantifying over a collection at all following points in time (syntax tbd: #4).
for all (name) in (expression) {
// statements
}
Where statements may include synchronous or asynchronous control structures.
Synchronous statements are executed whenever an element is added to the quantified collection.
var (numbers) = {};
for all (number) in (numbers) {
output line (number, "added to numbers");
}
add 1 to (numbers); // “1 added to numbers”
add 2 to (numbers); // “2 added to numbers”
This is equivalent to a listener for the add event:
on add (number) to (numbers) {
output line (…);
}
An else clause could handle the corresponding removal event.
var (numbers) = {};
for all (number) in (numbers) {
output line (number, "added to numbers");
} else {
output line (number, "removed from numbers");
}
add 1 to (numbers); // “1 added to numbers”
add 1 to (numbers); // No output; 1 is already present in the set
add 2 to (numbers); // “2 added to numbers”
remove 1 from (numbers); // “1 removed from numbers”
This is also equivalent to adding a listener.
on add (number) to (numbers) {
output line (…);
}
on remove (number) from (numbers) {
output line (…);
}
As in an on add (x) to (y) handler, (x) is in (y) evaluates to true in the true branch of a for all (x) in (y) block; inversely, (x) is not in (y) is true in the else branch, as in on remove (x) from (y).
Asynchronous statements are also executed on addition and removal, and have their usual effect of adding listeners. However, listeners added in the true branch of a for all statement must be removed in the (possibly implicit) else branch when the item is removed, that is, they must implicitly be predicated on the presence of the item in the collection, since we want them to fire only if the item is actually present, and not stick around if the item is never re-added. For example:
var (items) = {};
var (beans) = { name: "beans", uses: 2 };
var (beets) = { name: "beets", uses: 1 };
for all (item) in (items) {
when (item.uses <= 0) {
output line (item.name, "used up");
remove (item) from (items);
}
}
add (beans) to (items);
add (beets) to (items);
--beans.uses; // use a serving of beans
--beets.uses; // use a serving of beets; “beets used up”
remove (beans) from (items);
--beans.uses; // should *not* trigger the ‘when’
In terms of low-level events, at first blush this seems equivalent to an add event with an additional clause on any conditions of when &c. statements.
on add (item) to (items) {
when ((item) is in (items) & item.uses <= 0) {
// ~~~~~~~~~~~~~~~~~~~~~~
…
}
}
But suppose we used a whenever instead of a when.
for all (item) in (items) {
whenever (item.uses <= 0) {
output line ("all out of", item.name);
}
}
This listener would stick around even when (item) is in (items) becomes false, since it could become true again later. We can solve this in a few ways. One option is to provide access to listener IDs, and let them be managed explicitly—e.g. with pause, resume, and stop operations. This could be good to add anyway, although I’m not sure it’s necessarily the right fit here, since it needs a second listener to detect when to remove the first.
// Remove the listener the last time it’s evaluated,
// once its condition can no longer become true.
on add (item) to (items) {
whenever (item.uses <= 0) {
if ((item) is not in (items)) {
stop (this event); // ‘this event’ refers to the enclosing ‘when’
}
output line ("all out of", item.name);
}
}
// Remove the listener as soon as its condition can no longer become true.
on add (item) to (items) {
var (listener) = whenever (item.uses <= 0) {
output line ("all out of", item.name);
}
when ((item) is not in (items)) {
stop (listener);
}
}
I think a better option for this case may be to introduce a different expression like (item) in (items), which references the item within a collection, and expires when the item is removed, rather than just returning false like is in. If the listener depends on that cell, it will be garbage-collected when the item is removed. (Aside: (x) is in (y) could be implemented as something like defined ((x) in (y)).)
on add (item) to (items) {
when ((item) in (items); item.uses <= 0) {
…
}
}
Where a; b is like the comma operator in C, evaluating a and then b and returning the latter, but like other operators, it expires if either a or b expires.
This also needs to generalise to nestedfor all blocks that relate multiple collections together:
for all (bullet) in (bullets) {
for all (block) in (blocks) {
when (overlapping (bullet, block)) {
break (block);
}
}
}
Here, the when condition must depend on both(bullet) in (bullets) and (block) in (blocks).
This is to track a feature that’s stubbed out but not implemented yet, quantifying over a collection at all following points in time (syntax tbd: #4).
Where
statements
may include synchronous or asynchronous control structures.Synchronous statements are executed whenever an element is added to the quantified collection.
This is equivalent to a listener for the add event:
An
else
clause could handle the corresponding removal event.This is also equivalent to adding a listener.
As in an
on add (x) to (y)
handler,(x) is in (y)
evaluates to true in the true branch of afor all (x) in (y)
block; inversely,(x) is not in (y)
is true in theelse
branch, as inon remove (x) from (y)
.Asynchronous statements are also executed on addition and removal, and have their usual effect of adding listeners. However, listeners added in the true branch of a
for all
statement must be removed in the (possibly implicit)else
branch when the item is removed, that is, they must implicitly be predicated on the presence of the item in the collection, since we want them to fire only if the item is actually present, and not stick around if the item is never re-added. For example:In terms of low-level events, at first blush this seems equivalent to an
add
event with an additional clause on any conditions ofwhen
&c. statements.But suppose we used a
whenever
instead of awhen
.This listener would stick around even when
(item) is in (items)
becomes false, since it could become true again later. We can solve this in a few ways. One option is to provide access to listener IDs, and let them be managed explicitly—e.g. withpause
,resume
, andstop
operations. This could be good to add anyway, although I’m not sure it’s necessarily the right fit here, since it needs a second listener to detect when to remove the first.I think a better option for this case may be to introduce a different expression like
(item) in (items)
, which references the item within a collection, and expires when the item is removed, rather than just returningfalse
likeis in
. If the listener depends on that cell, it will be garbage-collected when the item is removed. (Aside:(x) is in (y)
could be implemented as something likedefined ((x) in (y))
.)Where
a; b
is like the comma operator in C, evaluatinga
and thenb
and returning the latter, but like other operators, it expires if eithera
orb
expires.This also needs to generalise to nested
for all
blocks that relate multiple collections together:Here, the
when
condition must depend on both(bullet) in (bullets)
and(block) in (blocks)
.