Scripting HelpersARCHIVE
A little piece of Roblox history. Preserved for everyone, one question at a time.Read about the archive →
✓ ANSWER ACCEPTED

What is the best way to use all of these different syntactical symbols in Lua?

I have been looking around a bit and have found that different symbols are used in various situations and wanted to know why. Here are some examples: First: Period and Colon. Why do some methods require me to use period and some the colon? Example code:

local folder = game.Workspace.Part.Folder -- uses a period in between each name why not a colon?
local folder = game.Workspace.Part:FindFirstChild("Folder") -- uses a colon after part why not a period?
script.Parent.Touched:Connect(function() -- colon after touched why? why not period?
	print("Hello")
end)
-- in some examples I have seen something like:
module:newBox() -- same questions apply
--also
module.foo()

[] this is shorter:

game.Workspace[Part] -- have seen this somewhere I dont even know if I did it right
-- why not just:
game.Workspace.Part?

and why do people sometimes do:

print "Hello"
-- and not
print("Hello")
-- or
:FindFirstChild "Part"
--and not
:FindFirstChild("Part")

and also why do people sometimes use semicolons at the end of a line and sometimes not? I thought that in Lua lines end on a line(Sorry if that is a bit vague)? Here is an example:

return 2 + 5 - 6 * 2;
-- can you not do 
return 2 + 5 - 6 * 2

I would love it if someone could explain all this to me. Thanks!

5 comments

0

You don't actually need any new lines in Lua, and some people are just used to other languages where a semicolon at the end of statements is required

0

Check the manual or Programming in Lua

0

I know the how I want to know the why and the whats best

0

You don't know the how, that's why you should read one of them. a:b(c) == a.b(a, c) and function a:b(c) end == function a(self, c) end.

0

Most of these are syntactic sugar.

2 answers

✓ Accepted answer

Introduction

: and . are both forms of syntactic sugar. Syntactic sugar are pretty much just syntax features which makes writing code more concise and quicker.

. Notation

The . operator allows us to index a table with a valid identifier. This means that the field we want to index must also be a legal variable name, here's an example:

local tab = {
	foo = "bar"
}

print(tab.foo)

This indexes tab for foo, and we can also omit the square brackets [] when declaring our key because foo can be used as a variable name, hence its a legal identifier.

print(tab.foo)

is analogous with

print(tab["foo"])

: Notation

The : operator allows us to call a member function of a userdatum/table. A member function is essentially just a function that's a member or element of an object (table/userdata). You can only use : on a member function, hence why you can't index a data member or regular attribute with : since they're not functions. This is why you can't do workspace:Baseplate, because Baseplate is a userdatum not a method.

Sample use

local tab = {}

function tab:foo()
end

-- invocation
tab:foo()

This function definition also makes use of syntax sugar (which we'll cover later). : implicitly passes self as it's first argument, and if it's used in a function definition, the first parameter is implicitly set to self. self is just a reference to the object we're calling the method on.

function tab.foo(self)
	print(self)
end

is analogous with

function tab:foo()
	print(self)
end

notice how in the second example we don't need to include self in the function signature.

Likewise:

tab.foo(tab)

is the same as

tab:foo()

and we don't need to include self in our argument list.

Function Declaration Sugar

You've probably used this form of sugar before when defining functions. Functions aren't inherently variables, they're a data type. Specifically they're actually "objects" or reference types, just like tables, userdata, strings and threads. Here's an example of it in use:

local function foo()
end

is the same as

local foo
foo = function()
end

Note that foo is just a variable with a reference to a function. We forward declare foo since if we did this:

local foo = function()
end

we wouldn't be able to make a recursive call, since the variable foo hasn't been defined yet.

Table Field Declaration Sugar

When we define a field/key thats a valid identifier, we can omit the square brackets when we define the key value pair, example:

local tab = {
	["foo"] = "bar"
}

is the same as

local tab = {
	foo = bar
}

Similar to using the . operator, we can't omit the square brackets if the key isn't a valid identifier (starts with a number, uses characters that aren't part of the input locale, etc).

3 comments

0

Good OOP post!

0

I forgot that self is passed into any table function defined with :

0

Thank you so much!

Because of Object Oriented Programming (OOP). I am no expert in OOP so please correct me at any costs. First thing is the . or [] when indexing an Instance (or any userdata or table for that matter). Most of the time you will want to use . for something when it is quite simple and doesn't have spaces:

print(workspace.Baseplate)

is the same as:

print(workspace["Baseplate"])

They will both print the Baseplate. However if the indexed name is something that needs to be constructed (I have no better way of wording this) then you'll want to use []:

local i = "Baseplate"

print(workspace.i) -- Won't work unless there is an object named i in the workspace

and

local i = "Baseplate"

print(workspace[i]) -- Will work as the lua parser will look up the variable i and index the workspace accordingly

Now for the case of . or : it's more complicated (and for people that don't know this you can use . for methods on Instances):

workspace.Destroy(workspace.Baseplate)

is the same as:

workspace.Baseplate:Destroy()

The second method is much cleaner and as a result is what most people use. Basically any Instance with that method can use . instead of :

Hope this answered your question!

In depth OOP section: Answering your question with module:newBox() and module.foo() how are these methods different

Well what I said earlier with Instance methods does not automatically apply to the a table with a couple methods. Example:

local i = {}
i.method = function()
	print("hey")
end

i.method()
i:method()

It will print hey twice so these two executions are not different yet. To make them different we need to have self get passed as a param.

local i = {}
function i:method() -- Correct syntax for self to pass
	print(self)
end

i:method()
i.method(i) -- We have to pass i into the method or else it will print nil

To make this more like Instancing we need to use meta tables. Okay this is where it's gonna get slightly complicated and it also helped that I already had an idea about what OOP was when learning about this. Let's first create a new method similar to Instance.new

local i = {}
i.somevalue = 1
i.new = function()
	local newi = i -- Not necessary right now but I will get into this soon
	return newi
end

local newi = i.new()
print(newi.somevalue)

This will print our value 1. Now we are going to make this table a meta table

local i = {}
i.__index = i -- Necessary for the meta table
i.somevalue = 1
i.new = function()
	local newi = i
	setmetatable(newi, i) -- Sets the meta table (I will explain this)
	return newi
end
function i:getvalue(value)
	return self[value]
end

local newi = i.new()
print(i.getvalue(newi, "somevalue")) -- Prints somevalue
print(newi:getvalue("somevalue")) -- Prints somevalue as well

Now do you see where this is going? It's getting pretty similar to the workspace.Destroy thing from earlier!

Setting the meta table allows similar behavior to Instancing. You can also use this to make a class module (or modules) in lua! You might find this useful one day so please consider giving this an upvote!

This discussion is preserved as it was. New answers, comments, and votes are closed.