Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Plenary list #109

Merged
merged 44 commits into from
Jun 9, 2021
Merged

Plenary list #109

merged 44 commits into from
Jun 9, 2021

Conversation

Joakker
Copy link
Contributor

@Joakker Joakker commented Mar 31, 2021

This PR adds an implementation of python-like lists, with a syntax similar to that of penlight's pl.List class. List-like tables are pretty barebones by default imo, so it would be nice to have an implementation that is a little bit more powerful and object oriented, providing basic operations and sintactic sugar. Over time, I plan on extending the implementation even further, wrapping vim's builtin list functions to work with it.

Since the methods are all accessed through the List metatable, calling vim.tbl_islist(List{1,2,3}) yields true.

Basic methods like index, append and count are also provided. I'm unsure as if to implement methods like map, and join, since they seem to be covered by plenary.functional.

There is a caveat that you cannot use the == operator directly between a List and a normal table, since it's part of lua's design, so an equal() method is provided as well as a workaround.

Usage:

local List = require'plenary.list'

local l1 = List{1, 2, 3}
print(l1) -- [1, 2, 3]

local l2 = l1 * 3
print(l2) -- [[1, 2, 3], [1, 2, 3], [1, 2, 3]]
l1:append(4)
print(l2) -- [[1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4]]

-- Works with any list-like table
print(List{'a', 'b'} .. {'c', 'd'}) -- ['a', 'b', 'c', 'd']

@tjdevries
Copy link
Member

I think let's call it py_list maybe?

@Joakker
Copy link
Contributor Author

Joakker commented Apr 1, 2021

Do you mean the class, the module or both?

@Conni2461
Copy link
Collaborator

I should probably mention that i did some work for a Array class that keeps track of the size, because table.insert is not that fast and tbl[idx] = val is just faster. See Conni2461@834056c I just haven't finished it.

Performance diff for scandir on homedir with hidden (100 runs)

scandir master scandir experimental fd
min: 0.907004 0.613642 0.706125
max: 1.284986 0.991585 0.915934
mean: 1.071764 0.797159 0.747807
median: 1.082431 0.814736 0.738394

@Joakker
Copy link
Contributor Author

Joakker commented Apr 1, 2021

Fair enough. Your implementation seems a bit more complete than mine 😅

@Conni2461
Copy link
Collaborator

Conni2461 commented Apr 1, 2021

tbl[idx] = val is only faster if you keep track of the size. Otherwise its the same.

I don't need to finish my implementation so you can do it and i just port the changes to scandir so its faster 😆 I would also put it in a collections dir or similar. So we can have other stuff as well. e.g. we can move tj linked list from telescope to here.

Edit: e.g. i need a next that doesn't reset on add (so i don't have to pop the first index thats super slow). And iterators (normal and reversed)

@Joakker
Copy link
Contributor Author

Joakker commented Apr 1, 2021

Ah ok 👍🏾

@Joakker
Copy link
Contributor Author

Joakker commented Apr 3, 2021

There we go. Some simple methods for iterating forwards and backwards. Let me know what you think 😃

@Conni2461
Copy link
Collaborator

👍 Good job. I will try to use this branch. I don't think you need to add a next function. I can emulate this myself. I am also not sure about the size storing, we could do some __index magic to make it work. At least that is what i was thinking about when i worked on it. I will look at this more closer pretty soon :)

@Joakker
Copy link
Contributor Author

Joakker commented Apr 3, 2021

Fair enough. I chose a weak table since we don't need to keep track of the actual instance, and when all other references to it are gone, the entry in lendb gets collected as well

@oberblastmeister
Copy link
Collaborator

I also think that storing the size inside of the list would be better. We don't need weak ref tables because it is a field inside of the list, which seems much simpler. Also, I don't know if hashing a table is a good idea, it might be slower than just hashing a primitive but I am not sure.

@oberblastmeister
Copy link
Collaborator

Should we have a plenary.structs for common datastructures? Then it can export different things. For example in the async_lib there is a Deque implementation. Maybe we could move them all to a common place?

@Joakker
Copy link
Contributor Author

Joakker commented Apr 4, 2021

py_list is already in a directory called collections so that could be used. Also, the main reason I use a weakref table is so that if we run it through vim.tbl_islist() it returns true. Still, if most people want to have it as a member, I can do that.

@oberblastmeister
Copy link
Collaborator

you could probably create a function that uses getmetatable to see if something is of the list class

@oberblastmeister
Copy link
Collaborator

Some other methods that would be cool to have, List:join would join the list with a string separator, List:clear would clear the table, and List:move is the same as table.move. Also the pack and unpack methods from lua would also be good to have.

@oberblastmeister
Copy link
Collaborator

I don't think you should add those higher order functions as they are just duplicates of the ones from the iterator library

@Joakker
Copy link
Contributor Author

Joakker commented May 15, 2021

I think I should, in case the user wants to chain them. Like so

local list1 = List {1, 2, 3, 4, 5, 6, 7, 8, 9, 0}

list1:reverse():filter_indexed(function(i, e)
  return i % 3 == 0 or e % 3 == 0
end):map(function(e)
  return tostring(e) .. ' little puppies'
end):for_each(print)
--[[ result:
0 little puppies
9 little puppies
8 little puppies
6 little puppies
5 little puppies
3 little puppies
2 little puppies
]]

This way, you have the choice of chaining the functions like in kotlin or java, or using a functional approach

@oberblastmeister
Copy link
Collaborator

That does the exact same thing as iterators already do. You can chain iterators

@Joakker
Copy link
Contributor Author

Joakker commented May 15, 2021

Oh, I hadn't realized. I'll change it now.

In another note, should we have a method List.from_iterator(r) which translates iterators, like ranges to List objects? I see there is an Iterator:to_list() and Iterator:to_listn() which transforms them into a regular list-like tables.

@oberblastmeister
Copy link
Collaborator

Also the functions you added are probably much slower, they allocate a new table for each function

@oberblastmeister
Copy link
Collaborator

Yes, you can create a from_iter. You could also create higher order functions that relate specifically to List, such as partition which could have the signature Iter -> (a -> Bool) -> (List, List)

@Joakker
Copy link
Contributor Author

Joakker commented May 18, 2021

A caveat I've run into, is that you can only use the partition() method directly, that is myList:iter():partition(), since the method doesn't seem to be preserved between chained calls

@Joakker Joakker mentioned this pull request May 21, 2021
@tjdevries
Copy link
Member

I think this looks good -- let's merge and we can play around with it as people use it. Sorry for the long delay :)

@tjdevries tjdevries merged commit c4dd6e7 into nvim-lua:master Jun 9, 2021
@Joakker
Copy link
Contributor Author

Joakker commented Jun 9, 2021

Thanks 😃

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants