The stackless model so far has been built on a generic and comprehensible building block called a YIELDER. I thought I'd walk through it a little.
To understand YIELDER, first look at GENERATOR
I think a generator is pretty easy to understand. It is like a function, but instead of RETURN it has something called YIELD. Each time YIELD is called the generator gives back the result but is left in a suspended state, and the next call to the generator will pick up right after the YIELD:
counter: generator [
let n: 0
cycle [
n: n + 1
yield n
]
]
>> counter
== 1
>> counter
== 2
Generators are building blocks, meant to be used with functions
Generators don't take parameters. So if you want to parameterize them, you should combine them with a function. Imagine you wanted to be able to specify a bump amount for your counter:
make-counter: func [bump] [
return generator [
let n: 0
cycle [yield n: n + 1]
]
]
>> counter: make-counter 5
>> counter
== 1
>> counter
== 6
>> counter
== 11
But functions aren't limited to being just "generator makers"...
For instance: functions can be generator wrappers, that actually delegate to the generator...or perhaps even destroy it and make new ones. Consider making a resettable counter, as @giuliolunati has in his GENERATE usermode generator:
counter: func [/reset <static> n (0) gen (null)] [
if reset [n: 0, return]
return reeval gen: default [
generator [
cycle [yield n: n + 1]
]
]
]
>> counter
== 1
>> counter
== 2
>> counter/reset
>> counter
== 1
>> counter
== 2
This gives a lot of flexibility in the design of generator interfaces. Considering the above example alone: what if you are in a situation where you think the counter/reset should have returned 1 instead of being a separate step that had no return result? Or maybe you think it should have returned what the last generator value was.
By making generators a "simplistic" building block, you're in control of these interface choices.
The YIELDER hybridizes with functions for efficiency
I said that generators don't have parameters or a function spec, but that is because they are a specialization of a version that does have a spec... called a YIELDER.
weird-print: yielder [x] [
cycle [
print ["Odd print:" x]
yield none
print ["Even print:" x]
yield none
]
]
>> weird-print "Hello"
Odd print: Hello
>> weird-print "Weird"
Even print: Weird
>> weird-print "World"
Odd print: World
This isn't anything you couldn't have achieved with a function that wrapped a generator, that held that generator statically and then sub-dispatched to it. It's just cleaner and more efficient. (Since GENERATOR is implemented as yielder [] [...generator body...]
it's kind of like the DOES analogue to FUNC.)
But this kind of gives you a sense of the parts box you have for building relatively efficient generator-type things.