lualua-table

Custom foreach implementation in Lua


I am trying to implement a custom iterator which works like table.foreach in Lua, but in more "syntax friendly" manner. I came across following construction (not sure if it's done purely in Lua or some kind of C "hack" in the Lua source code.

-- creature is a collection of Creature objects
-- "ps" is used as a filter variable

foreach creature cre "ps" do
    if cre.name ~= $name then
    -- code...
    end
end

I tried to play around with table.foreach, ipairs and so on - but can nowhere get the similar result to the mentioned code sample. Could you give me a hint how this could be implemented?


Solution

  • A new keyword requires either a preproccesor or modifying the language's grammar directly.

    An approximation of this syntax can already be easily achieved without leaving the confines of the language by writing an iterator - a function that when provided to the generic for continuously supplies values for the loop variables, until the first value it returns is nil.

    (More specifically, you usually write a function that creates (returns) an iterator. pairs and ipairs are examples of this.)

    next is a function that when provided a table and a key, returns the next key and its associated value.

    You can extend this with metatables and the __call metamethod to shorten the syntax required to construct the iterator (properly constructed classes / objects / prototypes (i.e., OOP) in Lua already rely heavily on metatables).

    All this means that the syntax

    for value in collection 'filter' do
        print(value)
    end
    

    is very achievable.

    The full example, with a very basic iterator:

    -- This is a live, proxied view into an existing table
    local function View(t)
        local methods = {}
    
        local function match(value, filter)
            return type(value) == 'string' and value:match(filter)
        end
    
        function methods:filter(filter)
            local key, value
    
            return function (_)
                repeat
                    key, value = next(t, key)
                until value == nil or match(value, filter)
    
                return value, key
            end
        end
    
        return setmetatable({}, {
            __index = methods,
            __metatable = false,
            __call = methods.filter
        })
    end
    
    local foo = View { 'c', 'a', 'b', 'a', 'c', 'd' }
    
    -- :filter via __call
    for value in foo '[ab]' do
        print(value)
    end
    
    print('-------')
    
    -- explicitly
    for value in foo:filter '[cd]' do
        print(value)
    end
    
    print('-------')
    
    -- additional index variable
    -- this is the inverse of the more typical `pairs` construct
    for value, key in foo '[ad]' do
        print(key)
    end
    
    a
    b
    a
    -------
    c
    c
    d
    -------
    2
    4
    6