Stuck with local variables

General discussion about LÖVE, Lua, game development, puns, and unicorns.
User avatar
DaedalusYoung
Party member
Posts: 407
Joined: Sun Jul 14, 2013 8:04 pm

Stuck with local variables

Post by DaedalusYoung »

Hi guys,
I keep reading everywhere that local variables are a million times better than globals. But my problem is that when a project is starting to get bigger, or is designed to get bigger in the future, I just don't know how to use them anymore.

I like to declare a bunch of tables in main.lua, outside any function, just at the top. I can make them all local and they can be accessed anywhere in main.lua then. But when I add code in a separate file and require or love.filesystem.load them into main.lua, they're all useless.

For example, I have a game with a couple of different levels. Every level I have in a separate lua file, and require it into main.lua whenever needed. But then I cannot access my local player.x and player.y variables in the level files anymore. Of course I can pass these as arguments, but that's going to be a long list of arguments. In love.keypressed, I would then have something like:

Code: Select all

function love.keypressed(key, unicode)
    if game.state == "level1" then
        level1.keypressed(key, unicode, player, mouse, window, colour, timer, flags, fade, volume)
    end
end
Well, then I can be proud of using only locals, but I'd rather use globals than to have to code like that.

Is there no way around this? Why does Lua even behave this way? To me, this:

Code: Select all

local x = 5

local function doublex()
    return x * 2
end

print(doublex())
is equivalent to this:

Code: Select all

--main.lua
local x = 5
local doublex

require 'filewithlotsofusefulfunctions.lua'

print(doublex())


--filewithlotsofusefulfunctions.lua
function doublex()
    return x * 2
end
But Lua disagrees. Even though I require in scope main.lua, the file contents are kept hostage in an alternate universe, with no hierarchical relation to the scope it's called in. Why?

So, what do I do? Just screw it all and use globals anyway, because they're a million times easier to use (and seem to have no negative impact on FPS either, and as I often read: don't optimise unless you have to), or bend my spine in 5 directions, because locals are 'better'?
User avatar
micha
Inner party member
Posts: 1083
Joined: Wed Sep 26, 2012 5:13 pm

Re: Stuck with local variables

Post by micha »

I always use global variable for things that are unique, such as the player and it's coordinates. But using globals for everything is like putting all your files into one folder, without subfolders: It's messy and you might overwrite something unintentionally.
So my advice is to use locals for local information. For example in a function that returns something, you often have intermediate results. Store these locally. In my code, I very often use the variable i,j,x and y for small local calculations and thus, these are always local in my code. But if you have one player and one player only, then it is very convenient to have it global.

Maybe just start coding and from time to time, check, what variables you have in global scope:

Code: Select all

for k,v in pairs(_G) do
  print(k .. ': ' .. v)
end
This might help you find the spots in your code, where unnecessary global variables are created.
User avatar
DaedalusYoung
Party member
Posts: 407
Joined: Sun Jul 14, 2013 8:04 pm

Re: Stuck with local variables

Post by DaedalusYoung »

Yes, I use locals for variables that serve no purpose anywhere else in the code, that won't change, but just accessing, using, and changing variables that should be visible everywhere is where I'm struggling to keep things local. I thought if I just kept it local in the scope of main.lua, then all files required in that same scope would still be able to pick them up, but they're not, and they're not even connected anywhere.

If I think about your analogy, then I have a root folder. If I create a file in there, then it can be accessed by all other files and folders in that root folder. But it doesn't seem to work like that. I have a root folder, then I require some external Lua script, and suddenly there's an additional root folder on a separate harddrive which has no physical connection to the main root folder. I would've thought that required Lua script would create a subfolder in my main root, just as if I had actually typed the script contents into the main.lua, but it's not. And I don't understand why not and I don't know how to fix that. Other than making just one 25 MB main.lua file with all my code cramped into. That can't be the preferred way.
User avatar
bartbes
Sex machine
Posts: 4946
Joined: Fri Aug 29, 2008 10:35 am
Location: The Netherlands
Contact:

Re: Stuck with local variables

Post by bartbes »

That's because you're thinking of dynamic scoping, where the scope depends on where things are called. Lua uses lexical (sometimes referred to as static) scoping, where the scope depends on where things are written.
teemid
Prole
Posts: 2
Joined: Mon Dec 30, 2013 11:53 pm

Re: Stuck with local variables

Post by teemid »

The reason for not using global variables is not for performance reasons. It has more to do with keeping sane as you project grows larger.
When everything is in the global scope you risk overwriting some of you earlier code by accident. If you called something 'x' somewhere in the code, you cannot use 'x' again somewhere else.
local variables have their scope limited to the block where they are declared. A block is the body of a control structure, the body of a function, or a chunk (the file or string with the code where the variable is declared).
- http://www.lua.org/pil/4.2.html

So this means that every time we enter a function, or a new file, we are in a new scope. So let's look at some code and figure out why it isn't working the way you expect it to. Here's a quick example that shows of the same problem.

Code: Select all

-- main.lua
x = 1

local x = 2

function num()
    local x = 3
    print(x) -- prints 'num function'
end

num()          -- prints 3
print(x)       -- prints 2
print(_G['x']) -- prints 1
So let's look at the code that didn't work as you expected. In the figure below each box is a scope. We can see everything that is inside our own box, and the ones we are inside of. We can however not see anything that we are not inside.

So when we are inside the doublex function we see everything inside the function, everything inside useful.lua, and everything inside the global scope, _G.

The outer scope is the global scope, which is represented by a table called _G in Lua, main.lua and useful.lua have their own scopes.

Image

So we see that both x and doublex lives in main.lua, because of the local keyword, no one outside the box can see these variables. Then we call require('useful') to bring in the doublex function from the file. What happens is that require goes into a new scope, the useful.lua scope. It goes through the file and sees the doublex function, which is defined as a global variable and so lua puts it in the global scope, _G.

We then jump back into the main.lua file and it's scope, we try to call the doublex function defined in useful.lua, but because we have our own doublex variable inside main.lua we can't see the doublex that is outside main.lua in the global scope, _G.

If you wanted to call the the doublex function, and even assign it to the local variable like so:

Code: Select all

_G['doublex']()
doublex = _G['doublex'] -- assign it to the local variable
doublex() -- then call it

This will still not work because the x inside the doublex function has no idea about what is defined in main.lua (it is outside its own box).

Because the x in main.lua was defined as local it does not exist in the global scope, this means that when lua tries to run the function doublex and starts looking for the x variable. Lua looks for 'x' in the doublex function body, the file which it is defined 'filewithlotsofusefulfunctions.lua' and the global scope, _G. Since it cannot find the variable in any of these places it gives up and gives you an error.
User avatar
DaedalusYoung
Party member
Posts: 407
Joined: Sun Jul 14, 2013 8:04 pm

Re: Stuck with local variables

Post by DaedalusYoung »

Thanks, I understand that. I would've found it more intuitive if the require'useful' line would not create a whole new scope, but just treated it as though the useful.lua file was an actual typed in chunk of code. I understand that's just not the way things work, it just makes no sense to me why anybody would want that. What's the use of requiring files which can not interact with local variables defined in the same scope they were called in? But that's not an interesting question anyway, because it changes nothing and there's no solution to be found in its answer.

I think I am quite able to keep sane working with larger projects with global variables. I don't just define a variable called x anywhere (in fact, I strictly use x always locally in for-loops). I organise my variables into relevant tables. Player is a table, holding player.x, player.y, player.speed, and all that stuff. There is only ever one player (not counting multiplayer games, I don't do that), so there's nothing confusing about it. If in any arbitrary file in any arbitrary function, I need to check, read or write any value relating to the player, I can simply access the player.var variable and do with it what I like.

I'm then using locals inside functions, for variables that have no need to be accessed or exist outside their scope. Temporary variables, for-loops, if-statements etc.

So if there's no performance issues, and global variables are easier to work with when using different files (as I imagine you should), then I don't see why I absolutely have to keep all variables local. If I want to access player.x in different files, then for me, the easiest and most intuitive way is to keep the table player global.
User avatar
riidom
Citizen
Posts: 74
Joined: Wed Jun 19, 2013 4:28 pm
Location: irgendwo an der Elbe
Contact:

Re: Stuck with local variables

Post by riidom »

You can also use dofile instead of require. I did that for quite some time, because I had issues with require. It works pretty much like "internally copy-paste all files into one, then run it", iirc.
teemid
Prole
Posts: 2
Joined: Mon Dec 30, 2013 11:53 pm

Re: Stuck with local variables

Post by teemid »

Ok, so the problem is not 'how does it work', but more 'why do it this way'?

So I recently made a simple eventhandler module while toying with Löve and Lua, somewhat simplified it looks like:

Code: Select all

--main.lua
local EventHandler = require('EventHandler')

local function printkey(key)
    print(key)
end

EventHandler.register('keypress', printkey)

--EventHandler.lua
local EventHandler = {}

local events = {}

local function helperFunction()
end

function EventHandler.register(event, ...)
     events[event] = callback -- add the callback object or function to the events table
end

function EventHandler.trigger(event, ...)
    for _,func in pairs(events[event]) do
        func(...)
    end
end

-- more functions in EventHandler

return EventHandler
Ok, so what does this do?

First of all, the require function now returns something we want to keep, namely the EventHandler table holding all the functions.
Since we are putting it into a local variable it will only be visible inside the main.lua file. So if I have some other code files that don't need to handle events they will never know about EventHandler unless they specifically ask for it.

Second the local variable and function, events and helperFunction respectively, are only visible to the EventHandler,
You cannot access events or call helperFunction() from main.lua. Why do we want this? Because events and helperFunction are only relevant to the inner workings of the EventHandler. There shouldn't really be any reason anything else in our code would want to interact with these things.

Third, EventHandler doesn't know anything about the printkey function, other than the fact that it is a function. All it does is call the function and
passes it some arguments when an event is triggered.

There are some more examples of modules here: http://lua-users.org/wiki/ModulesTutorial.

What we accomplish by doing this is limiting the places where the different parts of our code interact, and hiding things that are irrelevant to the use of our code. When you use EventHandler you don't really need to know how it stores the callback. What you really care about is that it calls your function when something happens, and that it does it somewhat efficently.

Not everything needs to be local, but by separating code as much as possible it becomes easier to use and read the code later.

All that being said, if you feel more productive using globals and your main goal is making a working game you should probably just go ahead.
There isn't really any better tools for learning than doing stuff, making mistakes and then correcting those mistakes.
User avatar
DaedalusYoung
Party member
Posts: 407
Joined: Sun Jul 14, 2013 8:04 pm

Re: Stuck with local variables

Post by DaedalusYoung »

Yeah, I see.

I don't really care about hiding irrelevant variables from other files. If they don't need them, then they don't use them, but if for some strange reason (or bugfix) I suddenly do need to access something I initially thought was irrelevant, I just want to go ahead and access it, without having to add it to a function call and then handle that argument in the location I need it.

Thanks guys. :)
Grubby
Prole
Posts: 35
Joined: Sat Jun 01, 2013 2:46 am

Re: Stuck with local variables

Post by Grubby »

Daedalus Young,

Glad you posted something like this. You voice some of my same observations. I too, have been wracking my brain trying to figure out Local vs Global vars. I've mostly read they are faster to access and can be considered an optimization. But then something like this shows up about as much as anything else:
The reason for not using global variables is not for performance reasons. It has more to do with keeping sane as you project grows larger.
When everything is in the global scope you risk overwriting some of you earlier code by accident. If you called something 'x' somewhere in the code, you cannot use 'x' again somewhere else.
Confusing? Seems so to me. In fact, almost everything you stated in the thread thus far pretty much follows my concerns. My project is getting huge way too fast. It IS much easier to do global then try and think about local usage as one codes. Granted, loop specific, function specific and/or throw away variables are easy enough to push to local, but the other stuff is where I *do* use global. Like you said, if ya can't determine an actual FPS hit (Like in my current simulation), I'm not at all inclined to try and define a dozen or more variables as local. Especially if I know those names by heart and never mis-type them... Or re-create them haphazardly. Furthermore, one might have to re-code to get that local thing going.

BTW -- Dare I say it, Lua programmers might be a cliquish bunch. Ducking... ;) Heh, didn't even consider "DoFile".

Anyway, thanks for bringing this up. I don't expect a concrete answer here at all.
Post Reply

Who is online

Users browsing this forum: No registered users and 183 guests