Skip to content

Instantly share code, notes, and snippets.

@deoxys314
Created October 9, 2022 23:35
Show Gist options
  • Save deoxys314/3f677179ec616283a8db701b30db8246 to your computer and use it in GitHub Desktop.
Save deoxys314/3f677179ec616283a8db701b30db8246 to your computer and use it in GitHub Desktop.
-- I have discovered a way to convert _any_ lua iterator into an
-- object which is lazily evaluated and which can be given behaviors
-- somewhat similar to the Iterator trait in Rust. Further, these
-- objects use metatables to allow for chained calls instead of the
-- Python-like ugly right-to-left series of calls (e.g.
-- `reduce(map(filter(range(1, 4), func), func), func)`)
--
-- This seems fairly elegent and obvious to me, so I am surprised I have not
-- seen any prior work in this area. I would love to be proven wrong, though!
--
-- Below is a short demonstration.
-- Table to hold our functions.
local iterx = {}
-- This is the function that converts a normal iterator to one that has our
-- "magic" UFCS functions and lazy evaluation (or at least, as lazy as the
-- original iterator is).
function iterx.magic(iter)
return setmetatable({}, {
__call = iter,
__index = iterx,
__name = 'Iterator<' .. tostring(iter) .. '>',
})
end
-- An implementation of `map` for our magic objects.
function iterx.map(iter, f)
-- If no function supplied, use identity function.
local func = f or function(i) return i end
return setmetatable({}, {
__call = function()
local val = iter()
if val ~= nil then
return func(val)
end
end,
__index = iterx,
__name = 'Map<' .. tostring(iter) .. '>',
})
end
-- An implementation of `take` for our "magic" iterators - we will grab at most
-- `n` elements.
function iterx.take(iter, n)
local max = n or 1
local count = 0
return setmetatable({}, {
__call = function()
count = count + 1
if count > max then
return nil
else
return iter()
end
end,
__index = iterx,
__name = 'Take<' .. tostring(iter) .. '>',
})
end
-- A helper function to exhaust and collect an iterator.
function iterx.collect(iter)
local t = {}
for thing in iter do
table.insert(t, thing)
end
return t
end
-- DEMO SECTION
-- A simple counter function that will go up infinitely, and also will be loud
-- about it, so we know when it's called.
local function LOUD_COUNTER()
local count = 0
return function()
count = count + 1
print('I am now returning ' .. count)
return count
end
end
local obj = iterx.magic(LOUD_COUNTER()):map(function(x) return x * 2 end)
:take(5)
print(obj)
print('Note that nothing from LOUD_COUNTER has been printed yet.')
local list = obj:collect()
print(
'Note that despite having an infinite iterator, there are only 5 items in this table.')
for _, val in ipairs(list) do
print(val)
end
-- Other useful functions are possible here too, like `filter`, `skip`,
-- `enumerate` and so on.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment