Lua tables do not come with Array methods like JavaScript and other popular languages. To make development easier, I created an Array module with ~40 methods for dealing with tables (especially arrays, but other tables work with many of the functions as well). Many of you will be excited to easily sort a dictionary table or perhaps you would just love to call traditional methods like Map, Filter, Find, Swap, etc.
There is also a published Roblox place with full test coverage and examples here, ArrayForLua - Roblox
I hurried to get this all done in the short time I had before I start my new job, so please help find any bugs or documentation errors. Comments and suggestions are encouraged.
btw, thanks for granting my join request to the forums. I’ll let this stand as my introduction post
It feels weird to use Array:Method(t) instead of Array.Method(t), while it’s t that gets actually used. and if you change it into Array.Method, we could do setmetatable(tab,Array) -- with __index tho and use tab:Map(func)
That’s true and I did considered that route. My concern is that metatable data is lost when tables are sent across filtering enabled boundaries. The current implementation seems like a safer solution in that context, unless I’m missing something. Admittedly, I only started playing/programming Roblox 3 months ago and had never seen Lua before that, so I am very aware I may miss some opportunities at first. I appreciate your comment.
The only downside is, as @einsteinK pointed out, it’s a little awkward to use. In my utility library there’s an object called FuncTable which simply wraps a Lua table and adds some functional methods to it such as Each, Filter, Sort, Map and Reduce. The nice thing about using a wrapper is that you can easily chain methods. For example I can do something like this (with some pre-made functions)
local topList = FuncTable.new(game.Players:GetPlayers())
:filter(PlayerScoreOver100)
:sort(PlayerScoreCompareDesc)
:map(GetPlayerName)
topList is the names of all players with a score over 100 sorted highest to lowest.
I really wish Lua had a decent unnamed function (closure) syntax so something like
function PlayerScoreCompareDesc(player1, player2)
return player1.score > player2.score
end
I agree with the comments around chaining and usability. As luck would have it, my start date has been delayed a day or two, so I will use that time to restructure the code and update the sample place.
By the time I finished writing the code (first pass), I discovered that setting actual execution context is version specific using one solution and not allowed by Roblox for the debug solution. What I ended up doing was not setting the actual context, but simply passing along scope… so scope would be the appropriate word. I’ve been writing JavaScript professionally for 20 years, so "this"naturally refers to scope in my brain. Wouldn’t calling the passed in scope, self make it confusing with the built in self that is magically available in Lua already?
Thank you!
When I got the notice that I was accepted to the community, I didn’t want to show up empty handed! Where I come from, we bring an apple pie to the party
It’s not really a built in thing. It’s just that
function A:B(...) end
is syntactic sugar for
function A.B(self,...) end
If you don’t specify self (and don’t use :) it doesn’t exist.
Basically, most people use self (and not this) when you’re referencing an object your method supposedly runs on/from. In your case it would be the API “object” (although it’s more like a class full of static methods)
Make that 21 years then… that’s right, my birthday is coming up later this month. (you had to remind me)
When I started learning web development, Netscape was calling JavaScript “LiveScript”, but they later concluded that name wasn’t confusing enough.
I totally missed the context in which the original post was referring (ironically). I thought the comment was regarding the way I was passing scope to callBack functions (Every, Some, etc). “this” as the first argument I understand… and in that case I would still argue that “context” is a more semantic word than self, but as self is what is generally accepted, I should probably use that.
I have updated the library to work either way (documentation is updated as well). You can wrap a table by calling the module on it: Array({1, 2, 3}):Sort(1) -- 3, 2, 1 or call the Array methods directly: Array.Sort({1, 2, 3}, 1) -- 3, 2, 1
Here’s a test implementation of your example working using the the updated library:
local Players = { -- mock player objects
{Name = 'ProfBeetle'}, {Name = 'HuotChu'}, {Name = 'nillagirl'}
}
local GetPlayers = function () -- mock service call
return Players
end
local Scores = {HuotChu = 40, nillagirl = 110, ProfBeetle = 90}
local topList = Array(GetPlayers())
:Filter(function (player) return Scores[player.Name] > 50 end)
:Sort(function (a, b) return Scores[a.Name] > Scores[b.Name] end)
:Map(function (player) return player.Name end)
print(topList:toString()) -- 'nillagirl','ProfBeetle'
Array.lua in the ArrayForLua project got a small bug fix today. IndexOf did not return -1 for empty tables as the assignment was unreachable with no elements to iterate. This now works as expected.
Â
As an aside, I am conflicted about Array.Sort on non-array tables. Currently, it returns an iterator. Sometimes, I would prefer to return a sorted, indexed array where each index holds a key/value pair. It would also be nice to provide the option to sort by key or sort by value (current sort is on value). Should I implement this, and if so, should I return the new “indexedArray” as the first or second return value?