fix(iter): remove special case totable for map-like tables

This was originally meant as a convenience but prevents possible
functionality. For example:

  -- Get the keys of the table with even values
  local t = { a = 1, b = 2, c = 3, d = 4 }
  vim.iter(t):map(function(k, v)
    if v % 2 == 0 then return k end
  end):totable()

The example above would not work, because the map() function returns
only a single value, and cannot be converted back into a table (there
are many such examples like this).

Instead, to convert an iterator into a map-like table, users can use
fold():

  vim.iter(t):fold({}, function(t, k, v)
    t[k] = v
    return t
  end)
This commit is contained in:
Gregory Anders 2023-04-19 07:05:04 -06:00
parent 6b96122453
commit 9489406879
3 changed files with 50 additions and 51 deletions

View File

@ -3037,6 +3037,19 @@ Iter:find({self}, {f}) *Iter:find()*
Iter:fold({self}, {init}, {f}) *Iter:fold()*
Fold an iterator or table into a single value.
Examples: >
-- Create a new table with only even values
local t = { a = 1, b = 2, c = 3, d = 4 }
local it = vim.iter(t)
it:filter(function(k, v) return v % 2 == 0 end)
it:fold({}, function(t, k, v)
t[k] = v
return t
end)
-- { b = 2, d = 4 }
<
Parameters: ~
• {init} any Initial value of the accumulator.
• {f} function(acc:any, ...):A Accumulation function.
@ -3297,9 +3310,7 @@ Iter:totable({self}) *Iter:totable()*
The resulting table depends on the initial source in the iterator
pipeline. List-like tables and function iterators will be collected into a
list-like table. If multiple values are returned from the final stage in
the iterator pipeline, each value will be included in a table. If a
map-like table was used as the initial source, then a map-like table is
returned.
the iterator pipeline, each value will be included in a table.
Examples: >lua
@ -3310,9 +3321,13 @@ Iter:totable({self}) *Iter:totable()*
-- { { 1, 2 }, { 2, 4 }, { 3, 6 } }
vim.iter({ a = 1, b = 2, c = 3 }):filter(function(k, v) return v % 2 ~= 0 end):totable()
-- { a = 1, c = 3 }
-- { { 'a', 1 }, { 'c', 3 } }
<
The generated table is a list-like table with consecutive, numeric
indices. To create a map-like table with arbitrary keys, use
|Iter:fold()|.
Return: ~
(table)

View File

@ -18,14 +18,6 @@ ListIter.__call = function(self)
return self:next()
end
--- Special case implementations for iterators on non-list tables.
---@class TableIter : Iter
local TableIter = {}
TableIter.__index = setmetatable(TableIter, Iter)
TableIter.__call = function(self)
return self:next()
end
---@private
local function unpack(t)
if type(t) == 'table' and t.__n ~= nil then
@ -185,10 +177,10 @@ end
--- Collect the iterator into a table.
---
--- The resulting table depends on the initial source in the iterator pipeline. List-like tables
--- and function iterators will be collected into a list-like table. If multiple values are returned
--- from the final stage in the iterator pipeline, each value will be included in a table. If a
--- map-like table was used as the initial source, then a map-like table is returned.
--- The resulting table depends on the initial source in the iterator pipeline.
--- List-like tables and function iterators will be collected into a list-like
--- table. If multiple values are returned from the final stage in the iterator
--- pipeline, each value will be included in a table.
---
--- Examples:
--- <pre>lua
@ -199,9 +191,13 @@ end
--- -- { { 1, 2 }, { 2, 4 }, { 3, 6 } }
---
--- vim.iter({ a = 1, b = 2, c = 3 }):filter(function(k, v) return v % 2 ~= 0 end):totable()
--- -- { a = 1, c = 3 }
--- -- { { 'a', 1 }, { 'c', 3 } }
--- </pre>
---
--- The generated table is a list-like table with consecutive, numeric indices.
--- To create a map-like table with arbitrary keys, use |Iter:fold()|.
---
---
---@return table
function Iter.totable(self)
local t = {}
@ -239,17 +235,21 @@ function ListIter.totable(self)
return Iter.totable(self)
end
---@private
function TableIter.totable(self)
local t = {}
for k, v in self do
t[k] = v
end
return t
end
--- Fold an iterator or table into a single value.
---
--- Examples:
--- <pre>
--- -- Create a new table with only even values
--- local t = { a = 1, b = 2, c = 3, d = 4 }
--- local it = vim.iter(t)
--- it:filter(function(k, v) return v % 2 == 0 end)
--- it:fold({}, function(t, k, v)
--- t[k] = v
--- return t
--- end)
--- -- { b = 2, d = 4 }
--- </pre>
---
---@generic A
---
---@param init A Initial value of the accumulator.
@ -783,7 +783,7 @@ function Iter.new(src, ...)
count = count + 1
local v = src[count]
if v == nil then
return TableIter.new(src)
return Iter.new(pairs(src))
end
t[count] = v
end
@ -827,25 +827,4 @@ function ListIter.new(t)
return it
end
--- Create a new TableIter
---
---@param t table Table to iterate over. For list-like tables, use ListIter.new instead.
---@return Iter
---@private
function TableIter.new(t)
local it = {}
local index = nil
function it.next()
local k, v = next(t, index)
if k ~= nil then
index = k
return k, v
end
end
setmetatable(it, TableIter)
return it
end
return Iter

View File

@ -3374,13 +3374,18 @@ describe('lua stdlib', function()
end)
it('handles map-like tables', function()
local t = { a = 1, b = 2, c = 3 }
local it = vim.iter(t):map(function(k, v)
local it = vim.iter({ a = 1, b = 2, c = 3 }):map(function(k, v)
if v % 2 ~= 0 then
return k:upper(), v * 2
end
end)
eq({ A = 2, C = 6 }, it:totable())
local t = it:fold({}, function(t, k, v)
t[k] = v
return t
end)
eq({ A = 2, C = 6 }, t)
end)
it('handles table values mid-pipeline', function()
local map = {