Libraries+, a library extension module

Libraries+ is a library extension module that adds functions that roblox/Lua does not provide us. You can get the module here. Expect updates every 1-2 weeks, whether it be removing a redundant function, extending a new library, or adding new functions to already-supporting libraries.

How to use

I recommend in the first line requiring it.

require(_M)()

This module returns a function that imports an import function that lets you import desired libraries.

e.g.

import("table")

will import the table extensions.

It can be called with multiple arguments, so

import("table", "math", "string") 

Gives that python feeling.

That will import the extensions for table, math and string.


Now onto documentation of functions.

math

float cbrt(float n)

Returns the cube root of n .

Example usage:

print(math.cbrt(64)) --> 4
print(math.cbrt(-64)) --> -4

float secant(float n)

Returns the secant of n .

Example usage:

print(math.secant(5)) --> 3.5253200858161

int factorial(int n)

Returns the factorial of n .

Example usage:

print(math.factorial(5)) --> 120

float triarea(int base, int height)

Returns the area of a triangle with base base and height height .

Example usage:

print(math.triarea(10, 10)) --> 50

bool isnan(Variant arg)

Returns a boolean that determines whether arg is NaN . Use this if you want to test a divisor before dividing or something, or before getting the square root of a negative number.

Example usage:

print(math.isnan(0/0)) --> true
print(math.isnan(math.huge*0)) --> true
print(math.isnan(math.sqrt(-1))) --> true

print(math.isnan(5)) --> false
print(math.isnan(math.huge)) --> false

int round(float n, float to = 1)

Returns n rounded to the nearest to.

Example usage:

print(math.round(5.4)) --> 5
print(math.round(1.6)) --> 2
print(math.round(75, 50)) --> 100

float roundtoplace(float n, int place = 1)

Returns n rounded to place places.

Example usage:

print(math.roundtoplace(1.5678654, 3)) --> 1.568

table factorsof(int n)

Returns a table of all the factors of n. This function will throw an exception if n is negative or if it is non-integral.

bool iseven(float n)

Returns a boolean that determines if n is even.

table

table copy(table t, bool deep)

Returns a shallow copy of t, and the deep argument determines if copy should make a deep copy of the given table.

Example usage:

local some_table =  { }
local t = { some_table, 50, 100}
local shallow_copy = table.copy(t)
local deep_copy = table.copy(t, true)

print(shallow_copy == t, deep_copy == t) --> false    false
print(shallow_copy[1] == some_table) --> true
print(deep_copy[1] == some_table) --> false

table shuffle(table t, bool make_copy)

Returns back t , but shuffled. The make_copy argument determines if a shuffled shallow copy should be returned instead.

table reverse(table t, bool make_copy)

Returns t , but reversed. So the first element is the last, the second element is the second-to-last, and so on. The make_copy argument determines if a reversed shallow copy should be made.

int getoccurrencesof(table t, Variant element)

Gets the occurrence count of element in t .

Example usage:

print(table.getoccurrencesof({ 5, { }, 2, "", 5, print, 5, 5, 0 }, 5)) --> 4

void cleardupes(table t)

Clears all duplicates in t.

Example usage:

local a = { 1, "hi", 5.3, "hi" }
table.cleardupes(a)
print(table.concat(a, ", "))
--> 1, hi, 5.3

bool isempty(table t)

Returns a boolean that determines if t is empty. Use this to test if a table is empty.

void clear(table t)

Clears t. Not much info is needed here, lol.

function zip(tuple table ...)

Python’s zip(). It takes a variable amount of tables, and when used in a generic for loop, the variables get the values of each table. Use this if you need to traverse multiple tables at once.

Example usage:

for a, b, c, d in table.zip({ 0, 1, 2 }, { "a", "b", "c" }, { 3, 4, 5 }, { "d", "e", "f" }) do
    print(a, b, c, d)
end

Output:

0 a 3 d
1 b 4 e
2 c 5 f

void output(table t, string delimiter = "")

Outputs a table in readable format. Yes, looks similar to JSON format, which HttpService:JSONEncode does, but this one allows for a custom delimiter.

It handles nested tables too (not that JSONEncode doesn’t, as it does, but my function does too).

table.output({ "incapaz", 15, { 10, 18, 2004 } }, ",\n")

Output:

["incapaz",
15,
[10,
18,
2004]]

function, table, int ripairs(table t)

reverse ipairs. It returns a function that when used in a generic for, will traverse a table in reverse order.

Example usage:

for pos, value in table.ripairs({ "d", nil, "b", "a", 0 }) do
    print(pos, value)
end

Output:

5 0
4 a
3 b

string

string title(string s)

Returns a title-cased version of s . Inspired by Python’s str.title().

Example use:

print(string.title("hello world!")) --> Hello World!

string zfill(string s, int goal)

Returns a new string with goal - #s amount of 0 s prepended to s . Inspired by Python’s str.zfill() . However, unlike Python, it will not do sign checks. If goal is smaller than or equal to #s, no filling is done.

print(string.zfill("Hello!", 10)) --> 0000Hello!

string join(string delimiter, string/table iterable) .

If iterable is a string, it will delimit each character with s , and return a delimited string (e.g. string.join(".", "hey") == "h.e.y" ). If iterable is a table, it behaves exactly like table.concat (it’s what join uses internally lol). Inspired by Python’s str.join().

Example usage:

print(string.join("\\", "Bye!!")) --> B\y\e\!\!
print(string.join(".", { "game", "Workspace", "Part" })) --> game.Workspace.Part

string digitalformat(int seconds)

Formats the number seconds into digital clock format and returns the string representation of that. ( 120 -> "2:00" ). Use this if you want a high-level representation of maybe a timer?

Example usage:

for i = 60, 0, -1 do
    print(string.digitalformat(i))
    wait(1)
end

string capitalize(string s)

Returns the first letter capitalized. Inspired by Python’s str.capitalize() .

Example usage:

print(string.capitalize("hi world!")) --> Hi world!

bool isalpha(string s)

Returns a boolean that determines if s is fully alphabetical. Whitespaces don’t count! Use this to test if a string is fully alphabetical. Inspired by Python’s str.isalpha().

Example usage:

print(string.isalpha("Hello World!")) --> false
print(string.isalpha("HelloWorld")) --> true

string interpolate(string s, table vars)

Pretty much JavaScript’s template constructor. The keys embedded in ${} should be keys in the second argument, which should be a dictionary.

Example usage:

print(string.interpolate("My name is ${name}, and I am ${height} tall!", { name = "incapaz", height = "6'2\"" }))
--> My name is incapaz, and I am 6'2" tall!

bool isvalididentifier(string s)

Returns a boolean that determines if s is a valid identifier.

Example usage:

local t = { } -- maybe you want only valid identifiers as keys in here
local function add_kv_pair(key, value)
    if string.isvalididentifier(key) then
        t[key] = value
    end
end

add_kv_pair("good", 5)
print(t.good) --> 5
add_kv_pair("bad!", 10)
print(t["bad!"]) --> nil
add_kv_pair(true, 15)
print(t[true]) --> nil

bool iswhitespace(string s)

Returns a boolean that determines if the string s is fully whitespace (empty string doesn’t count because EMPTY)

Maybe you are making a custom chat system, and you want to make sure the user doesn’t input just spaces.

if not string.iswhitespace(box.Text) then
    -- Send the message!
end

string strip(string s, string chars)

Returns a new string with leading and trailing whitespace removed, and the optional chars argument specifies to remove characters in the chars string.

Example usage:

print(string.strip("    Sup    ")) --> "Sup" (no trailing/leading whitespaces)
print(string.strip("    Sup People!    ", "po")) --> "Su Pele!" (the second argument isn't a sequence, it is just individual characters removed)

string formatthousands(int n)

Returns a string representation of commas separating thousands.

Maybe you want to display some currency on a GUI, and it can go up to thousands, so this is the function for you

-- assume they currently have 6578 money, so it becomes $6,578
currency_display.Text = "$" .. string.formatthousands(player.Money.Value)

Note that this does not account for negative values, you will have to do that yourself which is fairly easy.

rbx_instance

The functions here are functions you can apply to instances.

Instance WaitForChildOfClass(Instance instance, string class_name, float timeout)

This function waits for a child of a given class. If timeout is provided, it will wait timeout seconds and return the instance if found, otherwise nil. If WaitForChild and FindFirstChildOfClass exist, why doesn’t WaitForChildOfClass exist?

Instance WaitForChildWhichIsA(Instance instance, string class_name, float timeout)

Behaves like WaitForChildOfClass, but instead checks with IsA for inheritance.

table GetChildrenOfClass(Instance instance, string class_name)

Returns a table of children in instance that are of class class_name.

table GetChildrenWhichAreA(Instance instance, string class_name)

Behaves like GetChildrenOfClass, but instead checks with IsA for inheritance.

table GetDescendantsOfClass(Instance instance, string class_name)

Returns a table of descendants in instance that are of class class_name.

table GetDescendantsOfWhichAreA(Instance instance, string class_name)

Behaves like GetDescendantsOfClass, but instead checks with IsA for inheritance.

table GetAncestors(Instance instance)

Returns a table of the ancestors of instance.

If GetChildren and GetDescendants exist, why doesn’t GetAncestors exist?

table GetAncestorsOfClass(Instance instance, string class_name)

Returns a table of descendants in instance that are of class class_name.

table GetAncestorsWhichAreA(Instance instance, string class_name)

Behaves like GetAncestorsOfClass, but instead checks with IsA for inheritance.

void ClearAllChildrenExcept(Instance instance, table blacklist)

Clears all children in instance, except children with names in blacklist. Useful if, say, you want to clear the children in the workspace, workspace:ClearAllChildren() throws an exception as you cannot destroy the terrain.

table GetSiblings(Instance instance)

Returns a table of the siblings of instance. It does not include instance.

table GetSiblingsOfClass(Instance instance, string class_name)

Returns a table of the siblings of instance that are of class class_name.

table GetSiblingsWhichAreA(Instance instance, string class_name)

Behaves like GetSiblingsOfClass, but instead checks with IsA for inheritance.

bool HasProperty(Instance instance, string property)

Returns a boolean that determines whether or not the given instance has property property.

players

These two functions are more for working with players.

Instance GetPlayerFromString(string s)

This function allows you to get a player from a partial string, case-insensitive. So, you could get me with "inca", for example.

table GetPlayersFromString(string s)

Returns a table of players from a partial string. This function allows you to get multiple players that may have similar.

So, if you have 1 player named epicdragonslayer, and another named epicgamer422 (just made those up, if they exist I am not affiliated with them), both can be picked up with "epic", for example.

rbx_color3

These two functions are for working with Color3s. They just allow for addition and subtraction

Color3 add(Color3 lhs, Color3 rhs)

Returns a new Color3 with the sum of the given two RGB components. This function will throw an exception if the new Color3 has a component greater than 255.

Color3 sub(Color3 lhs, Color3 rhs)

Returns a new Color3 with the difference of the given two RGB components. This function will throw an exception if the new Color3 has a negative component.

UDim2

UDim2 fromTables(table x, table y)

This function isn’t really that special, it just allows you to construct a UDim2 from tables. It is a common mistake to call UDim2.new with table arguments: UDim2.new({ x_scale, x_offset }, { y_scale, y_offset }), and for those people who want to copy and paste it from the properties window.

CFrame

CFrame lookAt(Vector3 position, Vector3 look)

Returns a CFrame at position position oriented towards vector look. Since roblox deprecated the constructor that did this, CFrame.lookAt() is just a wrapper for the alternative roblox provided us.

Region3

Region3 fromPart(Instance part)

Returns a Region3 from a part. So you simply pass a reference to the part you want, and boom, the area the part covers is covered by the Region3. This function will throw an exception if part is not an Instance or if it’s not a BasePart.

Miscellaneous / incapaz_specials

Because there is no specific category for these. And they’re my specials :3

int printf(s, tuple Variant ...)

C’s printf. Just added it for fun.

local number = math.random(1, 10)
printf("The number is %d!", number)
-- roblox doesn't let us use io.write :(

Variant pathfromstring(string path)

This function allows you to get an instance, or a property value, from a string.

local remote_event = Instance.new("RemoteEvent")
remote_event.Name = "MyRemote"
remote_event.Parent = game:GetService("ReplicatedStorage")
print(pathfromstring("ReplicatedStorage.MyRemote") == remote_event) --> true

void import(tuple string ...)

Since Lua doesn’t have an import, I decided to make my own function that acts similar to it. Basically, when called with a library name, e.g. "table", it will “import” the functions in it into the namespace of the requiring script.

import "table" -- doesn't have to be called like this but it looks pretty epic so

local t = { }
insert(t, "xd")
print(t[1]) --> xd

It can also use a specific thing from a namespace, so if you specifically want table.zip for example, it will only add that:

import "table.zip"

print(insert, clear) --> nil    nil

for v1, v2 in zip( { 0, 1, 2 }, { "x", "y", "z" }) do
    print(v1, v2)
end

Other

_M performs others behaviour. It nullifies _G, shared, table.foreach, table.foreachi, string.len, and table.getn. We have modules for sharing things across scripts, we have generic for loops for traversing tables, the length operator for getting string/table length.

wait is modified to return false, and then the delta time. Just so people don’t misuse the conditional part and call wait in there.

The End

That is the end of documentation. More functions are to come, and more libraries are to be supported. I would really recommend feedback on the functions, and implementation detail suggestions.

47 Likes

This is pretty amazing, a bunch of random functions that are very useful, continue adding to this! I recommend taking inspiration from other engines for example.

2 Likes

mistake here, should be:

print(math.round(1.6)) --> 2
1 Like

Roblox has a function exactly like your table.includes (table.find).

Very nice module though!

1 Like

table.find was recently added in update 407, so these functions aren’t needed.

Also, your table.copy function will error when the array has too many elements. (unpack can only unpack so many elements)

if not deep then
    return {table.unpack(t)}
end

To avoid this, table.move should be used.

if not deep then
    return table.move(t,1,#t,1,table.create(#t))
end
3 Likes

currently working on something similar good work though

Why is the cube root NaN? The cube root of -1 is -1?

1 Like

That was what came out when (-1)^(1/3) operation was done.

@Halalaluyafail3 and @TacoBellSaucePackets, thanks for the replies. I will remove it once i get back on my computer.

@Halalaluyafail3, could you explain table.move and table.create? I was actually trying to work with the former, as I haven’t used roblox and just Lua in general. The official Lua site doesn’t explain the second-fourth arguments, and there’s absolutely no documentation on them in the roblox developer hub.

Some of these library extensions are useful as for some of my older code I wrote my own implementation for a feature I needed.

A few case examples are:

  • GetAncestors, getting a player instance from a partial segment of their username
  • Table Shuffle
  • A custom WaitForChildWhichIsA based off a simple repeat wait() until instance:FindFirstChildWhichIsA(...)
  • preforming operands on Color3 datatypes. (Why are Color3 operands not native in the first place like Vector operations!!)

Suggested Additions

Incase TL;DR
Add these:

  • math.root(number, nthRoot)
  • math.sumOfInteriorAngles(sides)
  • math.exteriorAngleOfRegularNthSidedPolygon(sides)
  • math.truncate(number, figure)

You should add a math.root function for general root operands. math.root(x, n) where x is the number you’re trying to find the nth root of. This can simple be done using something such as return x^(1 / n)

function math.cbrt(x)
	if x < 0 then
		return -((-x)^(1 / 3))
	end
	
	return x^(1 / 3)
end

Or a messier solution,

On the topic of root functions, you should be able to fix your math.cbrt by just using a simple if statement. If x < 3 then you should want to find the cube root of -x and then return the negative counterpart of the value of that expression.

I believe this works because x^(1/3) is symmetric about the origin. Therefore you could expect -(x^(1/3) to be equal to (-x)^(1/3)
image

There are also other geometrical functions you could add related to math.triarea. Perhaps a user might need to know the sum of interior angles in an nth sided shape math.sumOfInteriorAngles(sides)! Or how much each exterior angle is in a regular nth sided shape math.exteriorAngleOfRegularNthSidedPolygon(sides)!

I am also not aware of a function similar to math.truncate(number, figure). This could be used to well, truncate numbers. f(1.23456, 4) → 1.234

Other

Some of these functions seem useless such as tobool. With that I must add I think using was an interesting implementation that is based off abusing the fact that single parameters calls do not need to be wrapped in ().

I did something similar of what you did for using to create a very hacky language inside lua using brackets. if_then(expression) {ifTrueCallback, ifFalseCallback} Was just for funzies. It worked by returning lots of simple closures.

Here is an example code block!
local function if_then(evaluation)
    return function(input)
        local trueCallback = input[1]
        local falseCallback = input[2]

        if evaluation then
            return trueCallback
        end
        
        return falseCallback
    end
end

local function a()
    print"it's true"
end

local function b()
    print"it's false"
end

if_then(1 == 1) {a, b}

Overall I’m glad to see such a great community resource such as this one.

4 Likes

table.move is documented on the 5.3 manual.
https://www.lua.org/manual/5.3/manual.html#pdf-table.move
Equivalent to
a2[t],··· = a1[f],···,a1[e]
Default for a2 is a1
Returns a2

table.create was added by roblox, and creates an array with the size specified, with an optional argument for what to fill the values in with.
Creating the array with a specific size avoids preallocations.

function cbrt(x)
    local a = math.sign(x)
    return (math.abs(x)^(1/3))*a;
end

This should work. Use this instead of x^(1/3)
1 Like

I suggest adding the sign function (sgn, sign, signum) as I use it quite frequently and I believe it would be a useful addition to write it down quicker.

Example implementation:

function math.sign(x)
    return x == 0 and 0 or x/math.abs(x)
end

Otherwise great resource I will see myself using.

Roblox has had this since May 2017!

https://devforum.roblox.com/t/new-lua-functions-math-sign-and-math-clamp/37309

4 Likes

I see a math.secant but no math.cotanent and math.cosecant.
You should add those two to the library, as well as document some hidden behavior the library extension preforms!

And you should probably continue to use the naming scheme ROBLOX uses for their trigometric functions and use the abbreviation as the name of the function instead. math.sec (secant); math.csc (cosectant); and math.cot (cotangent).

1 Like

This is a very nice module and all, but I really do not think this code belongs in there without any actual documentation about this behavior.

env.shared, env._G, env.table.foreach, env.table.foreachi, env.table.getn, env.string.len = nil -- nullify deprecated/discouraged

local old_wait = wait
env.wait = function(n) -- goodbye to while wait(n) do
	return false, (old_wait(n)) -- second return after the delta time seems pointless, so call is wrapped in () -- local _, dt = wait(n)
end

This has the potential to cause issues for people who may not know why certain things are not working correctly after implementing your module. I understand why you implemented this behavior, but not why you chose not to document it.

3 Likes

Hey thanks for pointing this out. Was actually trying to document that, but the forum wouldn’t save the edits. It would just be stuck on loading. But now the edits have been published!

1 Like

I’m still working on this, but I’m at a point where I don’t know what else to add. So I came back here looking for ideas. If you have any, feel free to drop them below!

Perhaps you could clean the way the module is setup? It personally for me is pretty hard to read. Perhaps add separate modules inside the main one that are inherited by it.

Somewhat like this, where each library is a child of the main module:

Main
 |_ Math
 |_ String
 |_ Instance
 |_ ... 

Perhaps iterate over the children to collect the modules. Something like this:

local _M = {}

for i, v in pairs(script:GetChildren()) do
    local name = v.Name
    local module = require(v)

    _M[name] = module
end

local function setupEnvironment()
    local callingEnv = getfenv(2)
    
    for i, v in pairs(_M) do
        callingEnv[i] = v
    end
end

return setupEnvironment

Yup I was actually going to start making a separate project that used modules the “classic” way, but I’ll definitely split it into separate modules as that’s what they’re for.