2,875
edits
No edit summary |
No edit summary |
||
Line 427: | Line 427: | ||
=== reduce === | === reduce === | ||
Alias for [[#aggregate|Enumerator3.aggregate]] | Alias for [[#aggregate|Enumerator3.aggregate]] | ||
== Module Explanation == | |||
<syntaxhighlight lang="lua"> | |||
-------------------------------------------------------------------------------- | |||
-- Explanation on how this module functions / how it can be extended | |||
-------------------------------------------------------------------------------- | |||
---------------------------------------------------- | |||
-- Chaining functions and the Enumerator3 class | |||
---------------------------------------------------- | |||
--[[ | |||
The Enumerator3 class holds metadata for chaining functions and the context for the operation. Specifically, it holds three pieces of information: | |||
- The function to be executed next in the sequence. | |||
- Index of the Iterator Function: (which is 0 for ipairs and nil for pairs) | |||
- The Context Factory (A function that generates a fresh context) | |||
The Enumerator3 class ONLY serves as a storage for metadata with the sole purpose of chaining functions. This is also the reason why all chainable functions are added to Enumerator3. When a chainable function gets called, a new Enumerator3 instance gets created. The current (self) Enumerator3 metadata is passed to the newly created instance to form a chain (via the context object). The Enumerator3 class is ONLY used when functions are chained, NOT when executed. This is also why this module is as fast as it is. There are no calls made to retrieve data from the Enumerator3 instances when iterating. The context object is exclusively used for this purpose. | |||
When the chain of functions gets evaluated, the following happens. | |||
1. The next function in the chain is retrieved. | |||
2. The context object for the next function is created via the factory. | |||
3. The index for the next function is retrieved. | |||
4. The next function is invoked using the context and index. (Usually in a loop, until the index returns nil) | |||
The above 4 steps happen for every function in the chain. | |||
]] | |||
---------------------------------------------------- | |||
-- The context object and context factory | |||
---------------------------------------------------- | |||
--[[ | |||
Since some functions require data to be remembered for consecutive "nextFunc" calls, a context object is required. This can be seen, for example, for the map function. Where the map function stores an index (position) and passes it to the selector function. | |||
However, if the Enumerator3 object is reused or chained with a different function, the context from the previous iteration persists, leading to incorrect behavior. For example, in the map function, the index remains at the last iteration's position instead of restarting at 0. To prevent this, the context isn't passed directly to Enumerator3. Instead, it utilizes a function, known as a context factory, to generate a fresh context each time the function chain is evaluated. | |||
This is achieved via the wrapContext function. The context always contains the same fields in the same order: | |||
[1] State: Represents the current state of the function. | |||
[2] Next Function: The subsequent function in the chain. | |||
[3] Next Context (Factory): The factory function for creating the next context. | |||
Depending on the specific function used, additional fields may be added to the context object. For example, the map function appends: | |||
- A selector function as the 4th field. | |||
- An indexer as the 5th field. | |||
The sequence in which fields are added is crucial because it determines how they must be retrieved later. The fields added by a chainable function must align precisely with the fields expected by its implementation function. | |||
For example in the map (chainable) function we see: | |||
"Enumerator3.new(mapFunc, self.index, wrapContext2(self, selector, 0))" | |||
The wrapContext function adds the initial three fields (state, next function, next context factory), with the selector as the 4th field and the indexer (defaulting to 0) as the 5th field. | |||
The implementing function of map retrieves the fields in the exact order they were added: | |||
Example: The implementation of map retrieves all of these fields in the same order when called: | |||
local state = context[1] | |||
local nextFunc = context[2] | |||
local nextContext = context[3] | |||
local selector = context[4] | |||
local position = context[5] | |||
Maintaining this order is critical for ensuring that the context is correctly interpreted and that each function receives the data it expects. | |||
]] | |||
---------------------------------------------------- | |||
-- The state of the implementing function | |||
---------------------------------------------------- | |||
--[[ | |||
When an implementing function is invoked for the first time, the context object must be created using the provided factory function. During this initial call, the following occurs: | |||
- Check Initial State: The state is checked to see if it is 0. | |||
- Replace Context Factory: If the state is 0, the context factory function is replaced with the context itself via the initialiseContext function. | |||
- Set to Active State: The state is then set to 1, signifying that the function is now in its active iteration phase. | |||
On subsequent calls to the implementing function, the initialization step (state 0) is bypassed, and the function proceeds directly with the iteration step. Some implementing functions may require multiple states to handle more complex behaviors. | |||
When the state becomes -4, it signifies that the iteration has reached its end. At this point, the function returns nil to indicate to the caller that there are no further elements to process. | |||
]] | |||
---------------------------------------------------- | |||
-- Expanding upon the module | |||
---------------------------------------------------- | |||
--[[ | |||
If a new chainable function gets added (a function that returns an Enumerator3 instance) the following should be ensured: | |||
1. A chainable function and an implementation function pair must be implemented. | |||
2. The chainable function must be added to the Enumerator3 class to allow chaining. | |||
3. The chainable function must pass a context factory to the Enumerator3 constructor, with the parameters in the same order as the implementing function uses them. | |||
4. The implementing function must retrieve the required fields in the same order as the context factory specifies. | |||
5. The implementing function must create a new context via the factory function and store the context for future iterations. | |||
6. The state field can be used to track the state of the implementing function and change its behaviour. For instance, having an initialisation state and an iteration state. | |||
Simple functions such as the map/mapFunc and where/whereFunc can be used as a reference. | |||
If a new result function gets added (a function that iterates over the function chain and returns a direct result) the following should be ensured: | |||
1. Retrieve the nextFunc, contextFactory and index from the current object (self). | |||
2. Create the context by calling the contextFactory. | |||
3. Iterate over the chain by calling nextFunc using the context and index. | |||
If the index returned from nextFunc is nil, it means that the iteration has come to an end. | |||
]] | |||
</syntaxhighlight> |
edits