Why does Lua have no "continue" statement? - loops

I have been dealing a lot with Lua in the past few months, and I really like most of the features but I'm still missing something among those:
Why is there no continue?
What workarounds are there for it?

In Lua 5.2 the best workaround is to use goto:
-- prints odd numbers in [|1,10|]
for i=1,10 do
if i % 2 == 0 then goto continue end
print(i)
::continue::
end
This is supported in LuaJIT since version 2.0.1

The way that the language manages lexical scope creates issues with including both goto and continue. For example,
local a=0
repeat
if f() then
a=1 --change outer a
end
local a=f() -- inner a
until a==0 -- test inner a
The declaration of local a inside the loop body masks the outer variable named a, and the scope of that local extends across the condition of the until statement so the condition is testing the innermost a.
If continue existed, it would have to be restricted semantically to be only valid after all of the variables used in the condition have come into scope. This is a difficult condition to document to the user and enforce in the compiler. Various proposals around this issue have been discussed, including the simple answer of disallowing continue with the repeat ... until style of loop. So far, none have had a sufficiently compelling use case to get them included in the language.
The work around is generally to invert the condition that would cause a continue to be executed, and collect the rest of the loop body under that condition. So, the following loop
-- not valid Lua 5.1 (or 5.2)
for k,v in pairs(t) do
if isstring(k) then continue end
-- do something to t[k] when k is not a string
end
could be written
-- valid Lua 5.1 (or 5.2)
for k,v in pairs(t) do
if not isstring(k) then
-- do something to t[k] when k is not a string
end
end
It is clear enough, and usually not a burden unless you have a series of elaborate culls that control the loop operation.

You can wrap loop body in additional repeat until true and then use do break end inside for effect of continue. Naturally, you'll need to set up additional flags if you also intend to really break out of loop as well.
This will loop 5 times, printing 1, 2, and 3 each time.
for idx = 1, 5 do
repeat
print(1)
print(2)
print(3)
do break end -- goes to next iteration of for
print(4)
print(5)
until true
end
This construction even translates to literal one opcode JMP in Lua bytecode!
$ luac -l continue.lua
main <continue.lua:0,0> (22 instructions, 88 bytes at 0x23c9530)
0+ params, 6 slots, 0 upvalues, 4 locals, 6 constants, 0 functions
1 [1] LOADK 0 -1 ; 1
2 [1] LOADK 1 -2 ; 3
3 [1] LOADK 2 -1 ; 1
4 [1] FORPREP 0 16 ; to 21
5 [3] GETGLOBAL 4 -3 ; print
6 [3] LOADK 5 -1 ; 1
7 [3] CALL 4 2 1
8 [4] GETGLOBAL 4 -3 ; print
9 [4] LOADK 5 -4 ; 2
10 [4] CALL 4 2 1
11 [5] GETGLOBAL 4 -3 ; print
12 [5] LOADK 5 -2 ; 3
13 [5] CALL 4 2 1
14 [6] JMP 6 ; to 21 -- Here it is! If you remove do break end from code, result will only differ by this single line.
15 [7] GETGLOBAL 4 -3 ; print
16 [7] LOADK 5 -5 ; 4
17 [7] CALL 4 2 1
18 [8] GETGLOBAL 4 -3 ; print
19 [8] LOADK 5 -6 ; 5
20 [8] CALL 4 2 1
21 [1] FORLOOP 0 -17 ; to 5
22 [10] RETURN 0 1

Straight from the designer of Lua himself:
Our main concern with "continue" is that there are several other control structures that (in our view) are more or less as important as "continue" and may even replace it. (E.g., break with labels [as in Java] or even a more generic goto.) "continue" does not seem more special than other control-structure mechanisms, except that it is present in more languages. (Perl actually has two "continue" statements, "next" and "redo". Both are useful.)

The first part is answered in the FAQ as slain pointed out.
As for a workaround, you can wrap the body of the loop in a function and return early from that, e.g.
-- Print the odd numbers from 1 to 99
for a = 1, 99 do
(function()
if a % 2 == 0 then
return
end
print(a)
end)()
end
Or if you want both break and continue functionality, have the local function perform the test, e.g.
local a = 1
while (function()
if a > 99 then
return false; -- break
end
if a % 2 == 0 then
return true; -- continue
end
print(a)
return true; -- continue
end)() do
a = a + 1
end

I've never used Lua before, but I Googled it and came up with this:
http://www.luafaq.org/
Check question 1.26.
This is a common complaint. The Lua authors felt that continue was only one of a number of possible new control flow mechanisms (the fact that it cannot work with the scope rules of repeat/until was a secondary factor.)
In Lua 5.2, there is a goto statement which can be easily used to do the same job.

Lua is lightweight scripting language which want to smaller as possible. For example, many unary operation such as pre/post increment is not available
Instead of continue, you can use goto like
arr = {1,2,3,45,6,7,8}
for key,val in ipairs(arr) do
if val > 6 then
goto skip_to_next
end
# perform some calculation
::skip_to_next::
end

We can achieve it as below, it will skip even numbers
local len = 5
for i = 1, len do
repeat
if i%2 == 0 then break end
print(" i = "..i)
break
until true
end
O/P:
i = 1
i = 3
i = 5

We encountered this scenario many times and we simply use a flag to simulate continue. We try to avoid the use of goto statements as well.
Example: The code intends to print the statements from i=1 to i=10 except i=3. In addition it also prints "loop start", loop end", "if start", and "if end" to simulate other nested statements that exist in your code.
size = 10
for i=1, size do
print("loop start")
if whatever then
print("if start")
if (i == 3) then
print("i is 3")
--continue
end
print(j)
print("if end")
end
print("loop end")
end
is achieved by enclosing all remaining statements until the end scope of the loop with a test flag.
size = 10
for i=1, size do
print("loop start")
local continue = false; -- initialize flag at the start of the loop
if whatever then
print("if start")
if (i == 3) then
print("i is 3")
continue = true
end
if continue==false then -- test flag
print(j)
print("if end")
end
end
if (continue==false) then -- test flag
print("loop end")
end
end
I'm not saying that this is the best approach but it works perfectly to us.

Again with the inverting, you could simply use the following code:
for k,v in pairs(t) do
if not isstring(k) then
-- do something to t[k] when k is not a string
end

Why is there no continue?
Because it's unnecessary¹. There's very few situations where a dev would need it.
A) When you have a very simple loop, say a 1- or 2-liner, then you can just turn the loop condition around and it's still plenty readable.
B) When you're writing simple procedural code (aka. how we wrote code in the last century), you should also be applying structured programming (aka. how we wrote better code in the last century)
C) If you're writing object-oriented code, your loop body should consist of no more than one or two method calls unless it can be expressed in a one- or two-liner (in which case, see A)
D) If you're writing functional code, just return a plain tail-call for the next iteration.
The only case when you'd want to use a continue keyword is if you want to code Lua like it's python, which it just isn't.²
What workarounds are there for it?
Unless A) applies, in which case there's no need for any workarounds, you should be doing Structured, Object-Oriented or Functional programming. Those are the paradigms that Lua was built for, so you'd be fighting against the language if you go out of your way to avoid their patterns.³
Some clarification:
¹ Lua is a very minimalistic language. It tries to have as few features as it can get away with, and a continue statement isn't an essential feature in that sense.
I think this philosophy of minimalism is captured well by Roberto Ierusalimschy in this 2019 interview:
add that and that and that, put that out, and in the end we understand the final conclusion will not satisfy most people and we will not put all the options everybody wants, so we don’t put anything. In the end, strict mode is a reasonable compromise.
² There seems to be a large number of programmers coming to Lua from other languages because whatever program they're trying to script for happens to use it, and many of them want don't seem to want to write anything other than their language of choice, which leads to many questions like "Why doesn't Lua have X feature?"
Matz described a similar situation with Ruby in a recent interview:
The most popular question is: "I’m from the language X community; can’t you introduce a feature from the language X to Ruby?", or something like that. And my usual answer to these requests is… "no, I wouldn’t do that", because we have different language design and different language development policies.
³ There's a few ways to hack your way around this; some users have suggested using goto, which is a good enough aproximation in most cases, but gets very ugly very quickly and breaks completely with nested loops. Using gotos also puts you in danger of having a copy of SICP thrown at you whenever you show your code to anybody else.

Related

SPSS recoding variables data from multiple variables into boolean variables

I have 26 variables and each of them contain numbers ranging from 1 to 61. I want for each case of 1, each case of 2 etc. the number 1 in a new variable. If there is no 1, the variable should contain 2.
So 26 variables with data like:
1 15 28 39 46 1 12 etc.
And I want 61 variables with:
1 2 1 2 2 1 etc.
I have been reading about creating vectors, loops, do if's etc but I can't find the right way to code it. What I have done is just creating 61 variables and writing
do if V1=1 or V2=1 or (etc until V26).
recode newV1=1.
end if.
exe.
**repeat this for all 61 variables.
recode newV1 to newV61(missing=2).
So this is a lot of code and quite a detour from what I imagine it could be.
Anyone who can help me out with this one? Your help is much appreciated!
noumenal is correct, you could do it with two loops. Another way though is to access the VECTOR using the original value though, writing that as 1, and setting all other values to zero.
To illustrate, first I make some fake data (with 4 original variables instead of 26) named X1 to X4.
*Fake Data.
SET SEED 10.
INPUT PROGRAM.
LOOP Id = 1 TO 20.
END CASE.
END LOOP.
END FILE.
END INPUT PROGRAM.
VECTOR X(4,F2.0).
LOOP #i = 1 TO 4.
COMPUTE X(#i) = TRUNC(RV.UNIFORM(1,62)).
END LOOP.
EXECUTE.
Now what this code does is create four vector sets to go along with each variable, then uses DO REPEAT to actually refer to the VECTOR stub. Then finishes up with RECODE - if it is missing it should be coded a 2.
VECTOR V1_ V2_ V3_ V4_ (61,F1.0).
DO REPEAT orig = X1 TO X4 /V = V1_ V2_ V3_ V4_.
COMPUTE V(orig) = 1.
END REPEAT.
RECODE V1_1 TO V4_61 (SYSMIS = 2).
It is a little painful, as for the original VECTOR command you need to write out all of the stubs, but then you can copy-paste that into the DO REPEAT subcommand (or make a macro to do it for you).
For a more simple illustration, if we have our original variable, say A, that can take on integer values from 1 to 61, and we want to expand to our 61 dummy variables, we would then make a vector and then access the location in that vector.
VECTOR DummyVec(61,F1.0).
COMPUTE DummyVec(A) = 1.
For a record if A = 10, then here DummyVec10 will equal 1, and all the others DummyVec variables will still by system missing by default. No need to use DO IF for 61 values.
The rest of the code is just extra to do it in one swoop for multiple original variables.
This should do it:
do repeat NewV=NewV1 to NewV61/vl=1 to 61.
compute NewV=any(vl,v1 to v26).
end repeat.
EXPLANATION:
This syntax will go through values 1 to 61, for each one checking whether any of the variables v1 to v26 has that value. If any of them do, the right NewV will receive the value of 1. If none of them do, the right NewV will receive the value of 0.
Just make sure v1 to v26 are consecutively ordered in the file. if not, then change to:
compute NewV=any(vl,v1, v2, v3, v4 ..... v26).
You need a nested loop: two loops - one outer and one inner.

Summing array elements in Ruby

An exercise in Coderbyte is supposed to determine if some subset of integers in an array sum to the largest number in the array.
The following code seems to work on my computer, but when I submit it online, it seems to cause an endless loop. (There's never any output, regardless of the argument passed).
def arr_add?(arr)
a = arr.sort
lgst = a.pop
size = a.size
result = false
while size > 1
a.combination(size) {|c| result |= (c.inject {|r, a| r + a} == lgst)}
size -= 1
end
result.to_s
end
arr_add?([1, 2, 3, 4, 10, 14])
Any ideas why this might be the case?
I suspect that you are actually not running into an endless loop, but rather just take a really long time, because of the inefficiency of your algorithm.
def ArrayAdditionI(arr)
arr_size = arr.size
ary = arr.sort
largest = ary.pop
ary_size = arr_size - 1
combination_size = ary_size
result = false
while combination_size > 1
ary.combination(combination_size) {|combination|
result |= (combination.inject(
:+
) == largest)
}
combination_size -= 1
end
result.to_s
end
I introduced a new variable and renamed some others, so that it becomes easier to talk about the algorithm. I also reformatted it, to make the three nested "loops" more obvious.
Let's take a look at the algorithm.
The outer while loop is executed ary_size - 1 == arr_size - 2 times, with combination_size ranging from 2 to ary_size == arr_size - 1.
The combination "loop" is executed ary_size choose combination_size times, that's … well, a very quickly growing number.
The innermost "loop" (the operation performed by combination.inject) is executed combination_size - 1 times.
This gives a total execution count for the innermost operation of:
The Sum from 2 to arr_size - 1 of
arr_size - 1 choose combination_size times
combination_size - 1
In Wolfram Language, that's Sum[Binomial[a-1, c]*(c-1), c, 2, a-1], which Wolfram Alpha tells us is 2^(a-2) (a-3)+1, which is in O(2^n).
Playing around with the numbers a bit:
for 10 items, we have 1793 executions of the inject operation
for 15 items, we already have 98 305
for 20 items, we have 4 456 449
at 28 items, we cross the threshold to a billion operations: 1 677 721 601
for 1000 items, which I suspect is a somewhat reasonable input size CoderBytes might use, we have 2 670 735 203 411 771 297 463 949 434 782 054 512 824 301 493 176 042 516 553 547 843 013 099 994 928 903 285 314 296 959 198 121 926 383 029 722 247 001 218 461 778 959 624 588 092 753 669 155 960 493 619 769 880 691 017 874 939 573 116 202 845 311 796 007 113 080 079 901 646 833 889 657 798 860 899 142 814 122 011 828 559 707 931 456 870 722 063 370 635 289 362 135 539 416 628 419 173 512 766 291 969 operations. Oops.
Try your algorithm with arrays of length 5, 10, 15 (all instantaneous), 20 (a noticeable pause), and then 23, 24, 25 to get a feel for just how quickly the runtime grows.
Assuming that you could build a CPU which can execute the inner loop in a single instruction. Further assuming that a single instruction takes only a Planck time unit (i.e. the CPU has a frequency of roughly 20 000 000 000 000 000 000 000 000 000 000 THz). Further assuming that every single particle in the observable universe was such a CPU. It will still take more than the current age of the universe to execute your algorithm for an array of not even 500 items.
Note that with most of these programming puzzles, they are not actually programming puzzles, they are mathematics puzzles. They usually require a mathematical insight, in order to be able to solve them efficiently. Or, in this case, recognizing that it is the Subset sum problem, which is known to be NP-complete.
By the way, as a matter of style, here is (a slight variation of) your algorithm written in idiomatic Ruby style. As you can see, in idiomatic Ruby, it almost becomes a 1:1 translation of the English problem statement into code.
While it is asymptotically just as inefficient as your algorithm, it breaks early, as soon as the answer is true (unlike yours, will just keep running even if it already found a solution). (any? will do that for you automatically.)
def ArrayAdditionI(arr)
largest = arr.delete_at(arr.index(arr.max))
1.upto(arr.size).any? {|combination_size|
arr.combination(combination_size).any? {|combination|
combination.inject(:+) == largest
}
}.to_s
end
This is an alternative interpretation of the (unclear) problem statement:
def ArrayAdditionI(arr)
2.upto(arr.size).any? {|combination_size|
arr.combination(combination_size).any? {|combination|
combination.inject(:+) == arr.max
}
}.to_s
end
The code above is valid ruby code.
The result is "true".
It is a bit unusual perhaps in that while loops are somewhat rare to see/have, but since it is valid ruby code, it should work on that remote site too.
Contact whoever runs the online ruby interpreter at Coderbyte - their version appears to be incompatible with the MRI ruby.
Your code seems to count down; perhaps have a look at 10.downto(1) - replace with variables as appropriate.

Stata: assign numbers in a loop

I have a problem creating a loop in Stata.
I have a dataset in Stata where I classified my observations into 6 categories via variable k10. So k10 takes on values 1,2,3,4,5,6.
Now I want to assign each observation one value according to its class:
value 15 for k10=1
value 10 for k10=2
value 8 for k10=3
value 5 for k10=4
value 4 for k10=5
value 2 for k10=6
It is easy if I create a new variable w10 and do it like the following:
gen w10 =.
replace w10 = 15 if k10==1
replace w10 = 10 if k10==2
replace w10 = 8 if k10==3
replace w10 = 5 if k10==4
replace w10 = 4 if k10==5
replace w10 = 2 if k10==6
Now I tried to simplify the code by using a loop, unfortunately it does not do what I want to achieve.
My loop:
gen w10=.
local A "1 2 3 4 5 6"
local B "15 10 8 5 4 2"
foreach y of local A {
foreach x of local B {
replace w10 = `x' if k10= `y'
}
}
The loop assigns value 2 to each observation though. The reason is that the if-condition k10=`y' is always true and overwrites the replaced w10s each time until the end, right?
So how can I write the loop correctly?
It's really just one loop, not two nested loops. That's your main error, which is general programming logic. Only the last time you go through the inner loop has an effect that lasts. Try tracing the loops by hand to see this.
Specifically in Stata, looping over the integers 1/6 is much better done with forval; there is no need at all for the indirection of defining a local macro and then obliging foreach to look inside that macro. That can be coupled with assigning the other values to local macros with names 1 ... 6. Here tokenize is the dedicated command to use.
Try this:
gen w10 = .
tokenize "15 10 8 5 4 2"
quietly forval i = 1/6 {
replace w10 = ``i'' if k10 == `i'
}
Note incidentally that you need == not = when testing for equality.
See (e.g.) this discussion.
Many users of Stata would want to do it in one line with recode. Here I concentrate on the loop technique, which is perhaps of wider interest.

foreach using numlist of numbers with leading 0s

In Stata, I am trying to use a foreach loop where I am looping over numbers from, say, 05-11. The problem is that I wish to keep the 0 as part of the value. I need to do this because the 0 appears in variable names. For example, I may have variables named Y2005, Y2006, Var05, Var06, etc. Here is an example of the code that I tried:
foreach year of numlist 05/09 {
...do stuff with Y20`year` or with Var`year`
}
This gives me an error that e.g. Y205 is not found. (I think that what is happening is that it is treating 05 as 5.)
Also note that I can't add a 0 in at the end of e.g. Y20 to get Y200 because of the 10 and 11 values.
Is there a work-around or an obvious thing I am not doing?
Another work-around is
forval y = 5/11 {
local Y : di %02.0f `y'
<code using local Y, which must be treated as a string>
}
The middle line could be based on
`: di %02.0f `y''
so that using another macro can be avoided, but at the cost of making the code more cryptic.
Here I've exploited the extra fact that foreach over such a simple numlist is replaceable with forvalues.
The main trick here is documented here. This trick avoids the very slight awkwardness of treating 5/9 differently from 10/11.
Note. To understand what is going on, it often helps to use display interactively on very simple examples. The detail here is that Stata is happily indifferent to leading zeros when presented with numbers. Usually this is immaterial to you, or indeed a feature as when you appreciate that Stata does not insist on a leading zero for numbers less than 1.
. di 05
5
. di 0.3
.3
. di .3
.3
Here we really need the leading zero, and the art is to see that the problem is one of string manipulation, the strings such as "08" just happening to contain numeric characters. Agreed that this is obvious only when understood.
There's probably a better solution but here's how this one goes:
clear
set more off
*----- example data -----
input ///
var2008 var2009 var2010 var2011 var2012
0 1 2 3 4
end
*----- what you want -----
numlist "10(1)12"
local nums 08 09 `r(numlist)'
foreach x of local nums {
display var20`x'
}
The 01...09 you can insert manually. The rest you build with numlist. Put all that in a local, and finally use it in the loop.
As you say, the problem with your code is that Stata will read 5 when given 05, if you've told it is a number (which you do using numlist in the loop).
Another solution would be to use an if command to count the number of characters in the looping value, and then if needed you can add a leading zero by reassigning the local.
clear
input var2008 var2009 var2010 var2011 var2012
0 1 2 3 4
end
foreach year of numlist 08/12{
if length("`year'") == 1 local year 0`year'
di var20`year'
}

Does Lua optimize the ".." operator?

I have to execute the following code:
local filename = dir .. "/" .. base
thousands of times in a loop (it's a recursion that prints a directory tree).
Now, I wonder whether Lua concatenates the 3 strings (dir, "/", base) in one go (i.e., by allocating a string long enough to hold their total lengths) or whether it does this the inefficient way by doing it internally in two steps:
local filename = (dir .. "/") -- step1
.. base -- step2
This last way would be inefficient memory-wise because two strings are allocated instead of just one.
I don't care much about CPU cycles: I care mainly about memory consumption.
Finally, let me generalize the question:
Does Lua allocate only one string, or 4, when it executes the following code?
local result = str1 .. str2 .. str3 .. str4 .. str5
BTW, I know that I could do:
local filename = string.format("%s/%s", dir, base)
But I've yet to benchmark it (memory & CPU wise).
(BTW, I know about table:concat(). This has the added overhead of creating a table so I guess it won't be beneficial in all use cases.)
A bonus question:
In case Lua doesn't optimize the ".." operator, would it be a good idea to define a C function for concatenating strings, e.g. utils.concat(dir, "/", base, ".", extension)?
Although Lua performs a simple optimization on .. usage, you should still be careful to use it in a tight loop, especially when joining very large strings, because this will create lots of garbage and thus impact performance.
The best way to concatenate many strings is with table.concat.
table.concat lets you use a table as a temporary buffer for all the strings to be concatenated and perform the concatenation only when you are done adding strings to the buffer, like in the following silly example:
local buf = {}
for i = 1, 10000 do
buf[#buf+1] = get_a_string_from_somewhere()
end
local final_string = table.concat( buf )
The simple optimization for .. can be seen analyzing the disassembled bytecode of the following script:
-- file "lua_06.lua"
local a = "hello"
local b = "cruel"
local c = "world"
local z = a .. " " .. b .. " " .. c
print(z)
the output of luac -l -p lua_06.lua is the following (for Lua 5.2.2 - edit: the same bytecode is output also in Lua 5.3.6):
main (13 instructions at 003E40A0)
0+ params, 8 slots, 1 upvalue, 4 locals, 5 constants, 0 functions
1 [3] LOADK 0 -1 ; "hello"
2 [4] LOADK 1 -2 ; "cruel"
3 [5] LOADK 2 -3 ; "world"
4 [7] MOVE 3 0
5 [7] LOADK 4 -4 ; " "
6 [7] MOVE 5 1
7 [7] LOADK 6 -4 ; " "
8 [7] MOVE 7 2
9 [7] CONCAT 3 3 7
10 [9] GETTABUP 4 0 -5 ; _ENV "print"
11 [9] MOVE 5 3
12 [9] CALL 4 2 1
13 [9] RETURN 0 1
You can see that only a single CONCAT opcode is generated, although many .. operators are used in the script.
To fully understand when to use table.concat you must know that Lua strings are immutable. This means that whenever you try to concatenate two strings you are indeed creating a new string (unless the resulting string is already interned by the interpreter, but this is usually unlikely). For example, consider the following fragment:
local s = s .. "hello"
and assume that s already contains a huge string (say, 10MB). Executing that statement creates a new string (10MB + 5 characters) and discards the old one. So you have just created a 10MB dead object for the garbage collector to cope with. If you do this repeatedly you end up hogging the garbage collector. This is the real problem with .. and this is the typical use case where it is necessary to collect all the pieces of the final string in a table and to use table.concat on it: this won't avoid the generation of garbage (all the pieces will be garbage after the call to table.concat), but you will greatly reduce unnecessary garbage.
Conclusions
Use .. whenever you concatenate few, possibly short, strings, or you are not in a tight loop. In this case table.concat could give you worse performance because:
you must create a table (which usually you would throw away);
you have to call the function table.concat (the function call overhead impacts performance more than using the built-in .. operator a few times).
Use table.concat, if you need to concatenate many strings, especially if one or more of the following conditions are met:
you must do it in subsequent steps (the .. optimization works only inside the same expression);
you are in a tight loop;
the strings are large (say, several kBs or more).
Note that these are just rules of thumb. Where performance is really paramount you should profile your code.
Anyway Lua is quite fast compared with other scripting languages when dealing with strings, so usually you don't need to care so much.
In your example, whether the .. operator does optimization is hardly a problem for the performance, you don't have to worry about memory or CPU. And there's table.concat for concatenating many strings. (See Programming in Lua) for the use of table.concat.
Back to your question, in this piece of code
local result = str1 .. str2 .. str3 .. str4 .. str5
Lua allocates only one new string, check out this loop from Lua's relevant source in luaV_concat:
do { /* concat all strings */
size_t l = tsvalue(top-i)->len;
memcpy(buffer+tl, svalue(top-i), l * sizeof(char));
tl += l;
} while (--i > 0);
setsvalue2s(L, top-n, luaS_newlstr(L, buffer, tl));
total -= n-1; /* got 'n' strings to create 1 new */
L->top -= n-1; /* popped 'n' strings and pushed one */
You can see that Lua concatenate n strings in this loop but only pushes back to the stack one string in the end, which is the result string.
BTW, I know about table:concat(). This has the added overhead of creating a table so I guess it won't be beneficial in all use cases.
In this particular use case (and similar ones), you could consider reusing a table if you're concerned with creating lots of garbage tables:
local path = {}
...
-- someplace else, in a loop or function:
path[1], path[2] = dir, base
local filename = table.concat(path, "/")
path[1], path[2] = nil
...
you could even generalize this to a "concat" utility:
local rope = {}
function string_concat(...)
for i = 1, select("#", ...) do rope[i] = select(i, ...) end -- prepare rope
local res = table.concat(rope)
for i = 1, select("#", ...) do rope[i] = nil end -- clear rope
return res
end

Resources