Closed CodeSandwich closed 1 month ago
Something like local element, index = blocks:last()
would make sense, I think. I would extend it a little to allow an optional parameter that is returned if the list is empty: A call like blocks:last().content
would crash if blocks
is empty, but blocks:last{}.content
would just return nil. The example given above could then be made more robust by writing
(blocks.last{}.content or List{}):last()
Still not super pretty, but an improvement.
A common pattern is to use list:remove()
to get the last element, but that also removes that element from the list.
I'm not sure about get
. It's elegant and could provide the same default value functionality as last
, but the overlap with normal table indexing makes me uneasy. OTOH, Python dictionary users would feel right at home.
The design you outlined resonates with me well.
I'm not very experienced with Lua , but to me the overlap between plain lua tables indexes and indexes in get
doesn't seem problematic. Plain tables are a thin abstraction over mappings with the indexing operator []
completely bypassing it. OTOH List
provides a very focused API with a stronger abstraction, which would become even stronger if you were allowed to NOT bypass it when accessing items. I don't think that anybody will be surprised if they do list[-1] = "foo"
and list:get(-1) ~= "foo"
. As I said, I'm not very experienced and this API addition isn't really critical, it's purely a quality of life improvement.
The default value when the list is empty seems powerful, and adding it to just a single special case getter seems underwhelming. For example why shouldn't I be able to conveniently get the first item with a default? If you think that this feature is useful, then having it in a general purpose form, like in get
, would make a lot of sense, even if get
doesn't accept negative indexes and even if last
is added.
If you want to experiment a little while I make up my mind: place this in the init.lua
file in your pandoc data directory (see pandoc -v
).
local List = require 'pandoc.List'
local function get (t, k, default)
local value = t[k]
if value == nil then
return default
else
return value
end
end
local function last (t, default)
return get(t, #t, default)
end
local list_types = {List, getmetatable(pandoc.Blocks{}), getmetatable(pandoc.Inlines{})}
for _, lt in ipairs(list_types) do
lt.get = get
lt.last = last
end
Now you should be able to use :get
and :last
on lists.
I named the new function :at
(as in JavaScript) instead of :get
to make it clearer that it only works with indices. The next nightly build of pandoc should already come with the new function.
Thanks, it's awesome!
The problem
I'm using hslua in Pandoc filters, where accessing the last item in a list is fairly common. The standard way to do it in Lua is to do
list[#list]
, but it very quickly becomes unworkable when there are deeply nested lists. The code quickly ends up with monstrosities likeblocks[#blocks].content[#blocks[#blocks].content]
where the same identifiers are repeated multiple times, which hurts readability and increases the risk of introducing bugs. An alternative is to litter the code with intermediate variables, e.g.local content = blocks[#blocks].content
and thencontent[#content]
.The solution
The perfect solution would be for
List
to introduce a helper functionList:last
, which would return the last item ornil
if the list is empty. The above example could be then expressed asblocks:last().content:last()
, which is simple and elegant.A more elastic approach would be to add
List:get
which would accept negative indexes, similarly to other languages but unlike Lua tables indexing. The above example could then be expressed asblocks:get(-1).content:get(-1)
.