You only get...ONESHOT


#1

ONESHOT is a cool new function generator, which makes a DO-like function which holds state to run only once.

once: oneshot
for-each x [10 20 30] [
     once [print ["loop begins with" x]]
     print [x]
]

And that will give you:

loop begins with 10
10
20
30

So although ONCE was called for each step in the loop, it only ran what it was given one time. Unlike code that is run before the loop, it has access to the first element. (If BREAK and CONTINUE were “definitional”, it would also be able to modify them just once in the loop…which will likely be a relevant thing in the near future.)

It’s obviously not anything you couldn’t do with declaring a LOGIC! and putting in the test-and-flip code. But it’s shorter. And unlike wrapping that logic up in a helper function, this lets the code that you’re controlling change, so if one of the cases runs, the other case (with differing code) won’t run.

Plus, ONESHOT will “voidify” the evaluative result of what it’s given–like a conditional would. So NULL will always mean it didn’t run the code you gave it. So for instance, this means that for every time but the first, you could respond to it with an ELSE.

once: oneshot
for-each x [10 20 30] [
     once [print ["first:" x]] else [print ["rest:" x]]
]

That gives you:

first: 10
rest: 20
rest: 30

ONESHOT is actually a specialization of N-SHOT, which lets you specifiy a generic N. If that N is negative, then it will actually not run the code you give it N times before it starts running it.

>> anti: n-shot -2

>> anti [5 + 5]
// null

>> anti [6 + 6]
// null

>> anti [7 + 7]
== 14

>> anti [8 + 8]
== 16

@MarkEye/@Mark-hi suggested UPSHOT as the name for the specialization when it’s -1, so I went with that as the complement to ONESHOT for now.

Use with <static> and get lazy initialization

If you have something you want to happen only the first time you call a function, that’s now pretty easy:

operation: function [... <static> lazy (oneshot)] [
    lazy [
        ** "do expensive initialization here"
    ]
    ...
]

This is a shorter way of writing:

operation: function [... <static> initialized (false)] [
    if not initialized [
        ** "do expensive initialization here"
        initialized: true
    ]
    ...
]

Generating oneshots makes an ACTION!, and hence incurs more overhead than making a LOGIC! variable. But once they’ve been created, the execution time should be less than a conditional. e.g. for this case, lazy makes its decision faster than an IF, and doesn’t have to look up the words for the logic variables to be tested.