I've seen them used in many addons, but I can't grasp the idea of why they're being used. As I understand it, enclosing functions and variables inside a do block further limits the scope and hides whatever's in the block from the outside. What's the real purpose? Are there advantages or situations that necessitate this sort of implementation? I'm having trouble understanding why authors choose to do this by looking at code alone.
As I understand it, enclosing functions and variables inside a do block further limits the scope and hides whatever's in the block from the outside. What's the real purpose?
That is the purpose. Limiting the scope of your workhorse data as much as possible helps to prevent accidents.
If you had a particular block of code in mind, toss it out here and we can go over it.
Well, just as an example taken from PowerBar: (thanks Antiarc!)
do
local function flashBar(self)
self:SetAlpha((0.25 * math.sin(GetTime() * 7)) + 0.75)
end
local MAX_CP = 5
local cycleVal = 0
function mod:UNIT_ENERGY(token)
if UnitIsUnit("player", token) then
bar:SetLabel(UnitMana(token))
local cp = GetComboPoints("player")
if cp >= MAX_CP and db.flashAtFull and not self.flasher then
self.flasher = true
bar:AddOnUpdate(flashBar)
elseif cp < MAX_CP and self.flasher then
bar:RemoveOnUpdate(flashBar)
self.flasher = false
bar:SetAlpha(1)
end
bar:SetValue(UnitMana(token))
if UnitMana(token) == UnitManaMax(token) and not InCombatLockdown() and db.hideWhenFull and db.locked then
group:Hide()
else
group:Show()
end
end
end
end
Obviously, mod:UNIT_ENERGY is something that's going to be called very often, and FlashBar might be as well depending on the situation. Is there something I should be watching for as an author (e.g. repetitive function calls like this) for enclosing functions in do blocks?
It's nothing to do with how often the functions are called. The point of the arrangement there is that mod:MOD_ENERGY has a helper function (flashBar) and a constant parameter (MAX_CP) that don't need to be seen by other functions. Thus, those are made local to a do-end block. They're visible to mod:MOD_ENERGY (which is not local) but not to others.
Limiting scope of variables and function is a method of keeping code clean and sane. Readers that read the above piece of code can clearly tell that flashba() is a helper function specifically for the public mod:MOD_ENERGY() function and is used nowhere else.
Do-end blocks thus keep related groups of functions together, similar to how you would separate out a larger addon into several files to limit their variable/function scope to each file, rather than to each do-end block.
i've been slowly adopting the do..end block approach lately. i'm finding it helps me organize my code a bit better. a block might have only one exposed function and then have a lot of behind-the-scenes functions to actually do the heavy lifting.
I had always wondered about 'do' blocks, too. These are all great explanations of how they can be helpful!
Although it's unclear to me whether this methodology would have any noticable performance impact, it certainly does help visually delineate the scope and relationships of code inside the block.
Even without adding documentation (which many authors seem to skip or do as an afterthought), it vastly improves the human readability of the code, for me at least.
I had always wondered about 'do' blocks, too. These are all great explanations of how they can be helpful!
Although it's unclear to me whether this methodology would have any noticable performance impact, it certainly does help visually delineate the scope and relationships of code inside the block.
Even without adding documentation (which many authors seem to skip or do as an afterthought), it vastly improves the human readability of the code, for me at least.
Actually depending on situation it should be positive performance wise, since variables are disposed off just like within a function.
Actually depending on situation it should be positive performance wise, since variables are disposed off just like within a function.
are they tho? it "feels" like they should cuz the do..end kinda seems like the lua analog of C++ objects, but the locals inside a do..end are essentially static, no?
do
local count = 1
function mod:IncreaseCount()
count = count + 1
end
end
count would have to persist, no? and if you had multiple "objects" sharing that method, they'd all share the same count variable.
locals in a function can truly vanish because they aren't persistent. next function call, you simply re-allocate them on the stack.
Yes lilsparky. In your code snippet, when the do-end block finishes executing, the "count" variable goes out of scope. Lua then checks to see if any function closure still references the local variable "count". It finds one such reference in mod:IncreaseCount() and so instead of throwing away the "count" local variable from your function call stack (your whole addon file is a chunk executed as a function) at the end of executing your file, it moves the variable from your stack to your heap. The function then accesses "count" in the future via an indirection.
Note that you can only have up to 200 locals (a Lua-imposed limit so you don't clog up your stack).
While I understand how it's used... I've never really understood the NEED for do blocks. I've never once felt that I needed to use one in my code.
Going out of your way to hide things from yourself sounds like overly defensive programming in my opinion... There is certainly a need to hide your own stuff from external code, but I find that do...end blocks just make things harder to read in the end. I would split an addon into multiple files *primarily* (not only) for readability, not to prevent myself from accessing certain parts of it depending on where I am.
Going out of your way to hide things from yourself sounds like overly defensive programming in my opinion... There is certainly a need to hide your own stuff from external code, but I find that do...end blocks just make things harder to read in the end. I would split an addon into multiple files *primarily* (not only) for readability, not to prevent myself from accessing certain parts of it depending on where I am.
The do block is the equivalent of the following C code:
One thing that hasn't been said already about the benefit of do-blocks, is that you can safely use very generic variable names inside it. Creating a file scope upvalue called "i" is quite dangerous, in my opinion. Sometimes, you wanna give these upvalues a simple name, but you want to make sure that some code later on while not screw up your upvalues, and that forgetting to declare them locals won't lead to hard to detect bugs due to side-effects of accessing a unexpected value.
I agree with Valana_TB that a good way to restrict scope is to make a lot of small files. That's what I did on Talented, and that I try to do on other projects. It helps reduce the number of upvalue you have to account for.
I remember, when I hacked on PitBull 3, which was a single HUGE source file, with some upvalued (one called "self") was mostly on file-scope. This just makes determining what is what a huge pain later on in the code, because when you're inside a 500 lines function that is itself at lines 2K+, you don't know when some variable names pop up what it is and what it does, or if it's a typo.
When you're dealing with your own code, it's a lot easier to know what is what, but for others, it's difficult. Giving clues about the scope of variables helps a lot.
That is the purpose. Limiting the scope of your workhorse data as much as possible helps to prevent accidents.
If you had a particular block of code in mind, toss it out here and we can go over it.
Obviously, mod:UNIT_ENERGY is something that's going to be called very often, and FlashBar might be as well depending on the situation. Is there something I should be watching for as an author (e.g. repetitive function calls like this) for enclosing functions in do blocks?
cycleVal seems to be completely unused.
Do-end blocks thus keep related groups of functions together, similar to how you would separate out a larger addon into several files to limit their variable/function scope to each file, rather than to each do-end block.
Although it's unclear to me whether this methodology would have any noticable performance impact, it certainly does help visually delineate the scope and relationships of code inside the block.
Even without adding documentation (which many authors seem to skip or do as an afterthought), it vastly improves the human readability of the code, for me at least.
Actually depending on situation it should be positive performance wise, since variables are disposed off just like within a function.
are they tho? it "feels" like they should cuz the do..end kinda seems like the lua analog of C++ objects, but the locals inside a do..end are essentially static, no?
count would have to persist, no? and if you had multiple "objects" sharing that method, they'd all share the same count variable.
locals in a function can truly vanish because they aren't persistent. next function call, you simply re-allocate them on the stack.
or maybe i misinterpreted what you were saying...
Note that you can only have up to 200 locals (a Lua-imposed limit so you don't clog up your stack).
Slightly slower, but the difference is not noticeable unless you try to access it something like 1e7 times in a loop.
Going out of your way to hide things from yourself sounds like overly defensive programming in my opinion... There is certainly a need to hide your own stuff from external code, but I find that do...end blocks just make things harder to read in the end. I would split an addon into multiple files *primarily* (not only) for readability, not to prevent myself from accessing certain parts of it depending on where I am.
The do block is the equivalent of the following C code:
http://www.wowwiki.com/User:Ackis/LUACode#Passing_Tables
Might be of interest in this discussion.
I agree with Valana_TB that a good way to restrict scope is to make a lot of small files. That's what I did on Talented, and that I try to do on other projects. It helps reduce the number of upvalue you have to account for.
I remember, when I hacked on PitBull 3, which was a single HUGE source file, with some upvalued (one called "self") was mostly on file-scope. This just makes determining what is what a huge pain later on in the code, because when you're inside a 500 lines function that is itself at lines 2K+, you don't know when some variable names pop up what it is and what it does, or if it's a typo.
When you're dealing with your own code, it's a lot easier to know what is what, but for others, it's difficult. Giving clues about the scope of variables helps a lot.
Results aren't surprising.
local (stack access) > upvalue (heap access) > global (_G lookup)