So before I give a bit of background, is anyone else use these generator tools in languages other than APL and/or interested in how to make them fast and robust in Dyalog APL?
BACKGROUND and RATIONALE
I developed a simple utility to demonstrate generators in Dyalog APL. It's done using tokens (⎕TPUT and ⎕TGET) roughly with the schema at the end of this post. I think it is very powerful (though, like :While, :For and such, it can break up natural parallelism into chunks that are slow to interpret).
It is perfect for separating out logic for async. calls to web servers, random num generators, etc-- and many other tasks which are logically infinite (that is, whose termination is determined by logic in the caller AFTER they've been started up, e.g. based on content)-- e.g. "get me the next 100-digit prime after the one you already gave me, and keep going when I ask you to, until I have what I need."
But the drawback to my quick test implementation is that it is somewhat slow and at least under Mac/OS a tad buggy (interpreter dies when there is a lot of token action in miscellaneous threads, esp if normally recoverable errors occur). In Python, generator examples abound that speed up execution because of the decrease in overhead from not building up state and intermediate results (and the associated memory load). This same advantage SHOULD accrue in APL. This seems like a natural and simplifying concept for APL.
Another issue: while tokens work fine for this task (thank you), using tokens for production code where they are used by separately contributed utilities is risky. What if utility a and b both use token 12123? Perhaps separate token pools could be created or arbitrary token names (in place of numbers) could be used to make their use in utilities more rugged.
Anyone else experienced with generators in other languages? What do you think of their expressiveness and simplicity? Utility in APL?
SIMPLE SCHEMA
Code: Select all
⍝ <gen> is our generator. It is called via a utility called <generator>
⍝ which executes it in a separate thread, passing to it a namespace shared
⍝ with the caller-- which allows for the generator to Yield (send or receive
⍝ messages as if returning w/o losing state), to Signal errors and return.
⍝ It can also pass its generator namespace downstream, so its offspring can
⍝ do the heavy lifting in the same or a new thread...
∇r←genHandle gen initial
genHandle.Yield¨'first' 'second' (3 'complex') 'This is the last Yield'
r←0 ⍝ We're done
:RETURN
⍝ Normally, you'd do something more substantial
generate_a_lot_of_state_that_takes_time_to_recalculate
:While 1 ⍝ Forever...
genHandle.Yield some_very_complicated_result_based_on_that_state
:EndWhile
⍝ We needn't ever "return", but could
∇
⍝ Here's the simplest client...
g←gen generator 'some data'⍝ Start <gen> above as a generator with startup data
g.Next ⍝ Request the generator to yield its next result
first
g.Next
second
... ⍝ Soon, our generator is exhausted.
g.Next ⍝ No more data, either check g.Done (1) or
StopIteration Signalled ⍝ ... deal with the trappable signal
g.Next
∧
⍝ Here's a looping client ...
∇r← ClientForGen init; g
g←gen generator init ⍝ Start up the generator <gen>, <g> is the generator ns
⍝ (pseudo-"class") for communicating...
:TRAP 900 ⍝ Our signal number that the Generator is done...
:WHILE 1 ⍝ In python, there's a Signal that ends an iteration
⍝ If an iterator is active, it just catches the signal and ends.
g.Next ⍝ Read the data from-- or g.Send 'new data' to-- the generator
:ENDWHILE ⍝ If <gen> is done, the signal results in an error msg...
:ELSE
'We are done'
:ENDTRAP