Created
October 9, 2022 23:35
-
-
Save deoxys314/3f677179ec616283a8db701b30db8246 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- 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