Confusion about lua corountine's resume and yield function - lua

I am learning lua by this video tutorial, it has this piece of code:
co = coroutine.create(function()
for i=1,5 do
print(coroutine.yield(i))
end
end)
print(coroutine.resume(co,1,2))
print(coroutine.resume(co,3,4))
print(coroutine.resume(co,5,6))
print(coroutine.resume(co,7,8))
print(coroutine.resume(co,9,10))
print(coroutine.resume(co,11,12))
The output is like this:
true 1
3 4
true 2
5 6
true 3
7 8
true 4
9 10
true 5
11 12
true
But I don't understand how yield and resume passes parameters to each other and why yield doesn't output the first 1,2 that resume passes to it, could someone please explain? Thanks

Normal Lua functions have one entry (where arguments get passed in) and one exit (where return values are passed out):
local function f( a, b )
print( "arguments", a, b )
return "I'm", "done"
end
print( "f returned", f( 1, 2 ) )
--> arguments 1 2
--> f returned I'm done
The arguments are bound to the parameter names (local variables) by putting them inside the parentheses, the return values listed as part of the return statement can be retrieved by putting the function call expression in the right-hand side of an assignment statement, or inside of a larger expression (e.g. another function call).
There are alternative ways to call a function. E.g. pcall() calls a function and catches any runtime errors that may be raised inside. Arguments are passed in by putting them as arguments into the pcall() function call (right after the function itself). pcall() also prepends an additional return value that indicates whether the function exited normally or via an error. The inside of the called function is unchanged.
print( "f returned", pcall( f, 1, 2 ) )
--> arguments 1 2
--> f returned true I'm done
You can call the main function of a coroutine by using coroutine.resume() instead of pcall(). The way the arguments are passed, and the extra return value stays the same:
local th = coroutine.create( f )
print( "f returns", coroutine.resume( th, 1, 2 ) )
--> arguments 1 2
--> f returns true I'm done
But with coroutines you get another way to (temporarily) exit the function: coroutine.yield(). You can pass values out via coroutine.yield() by putting them as arguments into the yield() function call. Those values can be retrieved outside as return values of the coroutine.resume() call instead of the normal return values.
However, you can re-enter the yielded coroutine by again calling coroutine.resume(). The coroutine continues where it left off, and the extra values passed to coroutine.resume() are available as return values of the yield() function call that suspended the coroutine before.
local function g( a, b )
print( "arguments", a, b )
local c, d = coroutine.yield( "a" )
print( "yield returned", c, d )
return "I'm", "done"
end
local th = coroutine.create( g )
print( "g yielded", coroutine.resume( th, 1, 2 ) )
print( "g returned", coroutine.resume( th, 3, 4 ) )
--> arguments 1 2
--> g yielded true a
--> yield returned 3 4
--> g returned true I'm done
Note that the yield need not be directly in the main function of the coroutine, it can be in a nested function call. The execution jumps back to the coroutine.resume() that (re-)started the coroutine in the first place.
Now to your question why the 1, 2 from the first resume() doesn't appear in your output: Your coroutine main function doesn't list any parameters and so ignores all arguments that are passed to it (on first function entry). On a similar note, since your main function doesn't return any return values, the last resume() doesn't return any extra return values besides the true that indicates successful execution as well.

co = coroutine.create(function()
for i=1,5 do
print(coroutine.yield(i))
end
end)
We start the coroutine the first time using:
print(coroutine.resume(co,1,2))
it will run until the first yield. our first resume call will return true and the parameters of yield (here i = 1) which explains the first output line.
our coroutine is now suspended. once we call resume a second time:
print(coroutine.resume(co,3,4))
your first yield finally returns and the parameters of your current resume (3,4) will be printed. the for loops second iteration begins, coroutine.yield(2) is called, supending the coroutine which again will make your last resume return true, 2 and so on
So actually in your example coroutine.resume(co) would be sufficient for the first call as any further arguments are lost anyway.

The reason we see this behavior is subtle, but it has to do with a mismatch of "entering" and "exiting" yield statements. It also has to do with the order in which print and yield are called within your anonymous function.
Let's imagine a graph of the execution of print(coroutine.yield(i)) vs. iteration.
On the first iteration, we have coroutine.resume pass 1 and 2 to the coroutine. This is the origin point, so we are not picking up from a former yield, but rather the original call of the anonymous function itself. The yield is called inside the print, returning i=1 but leaving print uncalled. The function exits.
What follows is the suspension of the function for a time before we see resumption by the next coroutine.resume.This resume passes 3 and 4. The function picks up at the last yield. Remember that the print function was left uncalled as the 1st yield was called first and exited the program? Well, execution returns inside the print, and so print now gets called, but this time returning 3 and 4 as these were the latest values to be transferred over. The function repeats again, calling yield before print, returning i=2.
If we go down the iterations we will more definitely the pattern underlying why we didn't see the 1 and 2. Our first iteration was an "exiting" yield unpaired with a corresponding "entering yield." This corresponds to the 1st time execution of the coroutine co.
We might expect the last yield to also go unpaired, but the difference is that we'll have an "entering" yield which is unpaired as opposed to an "exiting" yield which is unpaired. This is because the loop would have already finished. This explains why we see 11 12 followed by true followed with no "exiting" yield return.
This sort of situation is independent of the parity (even/oddness) of the for loop inside. What only matters are the resume and yield pairs and the manner in which they are handled. You have to appreciate that yield won't return a value within the function on the first call of resume which is being used to call the function inside the coroutine in the first place.

Related

Strange behavior caused by debug.getinfo(1, "n").name

I learned how to get the function name inside a function by using debug.getinfo(1, "n").name.
Using this feature, I found out the strange behavior in Lua.
Here's my code:
function myFunc()
local name = debug.getinfo(1, "n").name
return name
end
function foo()
return myFunc()
end
function boo()
local name = myFunc()
return name
end
print(foo())
print(boo())
Result:
nil
myFunc
As you can see, the function foo() and boo() calls the same function myFunc() but they return different results.
If I replace debug.getinfo(1, "n").name with other string, they return the same results as expected but I don't understand the unexpected behavior caused by using the debug.getinfo().
Is it possible to correct myFunc() function so calling both foo() and boo() functions return the same result?
Expected result:
myFunc
myFunc
In Lua, any return statement of the form return <expression_yielding_a_function>(...) is a "tail call". Tail calls essentially don't exist in the call stack, so they take up no additional space or resources. The function you call effectively gets erased from the debug information.
Is it possible to correct myFunc() function so calling both foo() and boo() functions return the same result?
Um... yes, but before I tell you how, allow me to try to convince you not to do this.
As previously mentioned, tail calls are part of the Lua language. The removal of tail calls from the stack is not an "optimization" any more than it is an "optimization" for a for loop to exit when you use break. It is a part of Lua's grammar, and Lua programmers have just as much a right to expect a tail call to be a tail call as they have the right to expect break to exit loops.
Lua, as a language, specifically states that this:
local function recursive(...)
--some terminating condition
return recursive(modified_args)
end
will never, ever, run out of stack space. It will be just as stack space efficient as performing a loop. This is a part of the Lua language, just as much a part of it as the behavior of for and while.
If a user wants to call your function via a tail call, that is their right as the user of a language that makes tail calls a thing. Denying users of a language the right to use the features of that language is rude.
So don't do it.
Furthermore, your code suggests that you are attempting to rely on functions having names. That you're doing something significant and meaningful with those names.
Well, Lua is not Python; Lua functions do not have to have names, period. As such, you should not write code that meaningfully relies upon the name of a function. For debugging or logging purposes, fine. But you should not break user expectations just for debugging and logging. So if the user made a tail call, just accept that's what the user wanted and that your debugging/logging will suffer slightly.
OK, so, do we agree that you shouldn't do this? That Lua users have the right to tail calls, and you don't have the right to deny them? That Lua functions are not named and you shouldn't write code that requires them to maintain a name? OK?
What follows is terrible code that you should never use! (in Lua 5.3):
function bypass_tail_call(Func)
local function tail_call_bypass(...)
local rets = table.pack(Func(...))
return table.unpack(rets, rets.n)
end
return tail_call_bypass
end
Then, simply replace your real function with the return of the bypass:
function myFunc()
local name = debug.getinfo(1, "n").name
return name
end
myFunc = bypass_tail_call(myFunc)
Note that the bypass function has to build an array to hold the return values, then unpack them into the final return statement. This obviously requires additional memory allocations that don't have to happen in regular code.
So there's another reason not to do this.
You can run your code through luac -l -p
...
function <stdin:6,8> (4 instructions at 0x555f561592a0)
0 params, 2 slots, 1 upvalue, 0 locals, 1 constant, 0 functions
1 [7] GETTABUP 0 0 -1 ; _ENV "myFunc"
2 [7] TAILCALL 0 1 0
3 [7] RETURN 0 0
4 [8] RETURN 0 1
function <stdin:10,13> (4 instructions at 0x555f561593b0)
0 params, 2 slots, 1 upvalue, 1 local, 1 constant, 0 functions
1 [11] GETTABUP 0 0 -1 ; _ENV "myFunc"
2 [11] CALL 0 1 2
3 [12] RETURN 0 2
4 [13] RETURN 0 1
Those are the two function that are of interest to us: foo and boo
As you can see, when boo calls myFunc, it's just a normal CALL, so nothing interesting there.
foo, however, does something called a tail call. That is, the return value of foo is the return value of myFunc.
What makes this kind of call special is that there is no need for the program to jump back into foo; once foo calls myFunc it can just hand over the keys and say "You know what to do"; myFunc then returns its results directly to where foo was called. This has two advantages:
The stack frame of foo can be cleaned up before myFunc is called
once myFunc returns, it doesn't need two jumps to return to the main thread; only one
Both of those are insignificant in examples like yours, but once you have a chain of lots and lots of tail calls, it becomes significant.
The downside of this is that, once the stack of foo gets cleaned up, Lua also forgets all the debugging information associated with it; it only remembers that myFunc was called as a tail call, but not from where.
An interesting side note, is that boo is almost also a tail call. If Lua didn't have multiple return values, it'd be exactly identical to foo, and a smarter compiler like LuaJIT might compile it to a tail call. PUC Lua won't though, since it needs a literal return some_function() to recognize the tail call.
The difference is that boo only returns the first value returned by myFunc, and while in your example, there will only ever be one, the interpreter can't make that assumption (LuaJIT might make that assumption during JIT compilation, but that's beyond my understanding)
Also note that, technically, the word tail call just describes a function A directly returning the return value of another function B.
It often gets used interchangeably with tail call optimization, which is what the compiler does when it re-uses the stack frame and turns the function call into a jump.
Strictly speaking, C (for example) has tail calls, but it has no tail call optimization, meaning something like
int recursive(n) { return recursive(n+1); }
is valid C code, but will eventually cause a stack overflow, while in Lua
local function recursive(n) return recursive(n+1) end
will just run forever. Both are tail calls, but only the second gets optimized.
EDIT: As always with C, some compilers may, on their own, implement tail call optimization, so don't go around telling everyone that "C never ever does it"; it's just not a requried part of the language, while in Lua it's actually defined in the language specification, so it's not Lua until it has TCO.
This is a result of tail call optimisation, which Lua does.
In this case, Lua translates the function call into a "goto" statement, and does not use any extra stack frame to perform the tail call.
You can add traceback statement to check it:
function myFunc()
local name = debug.getinfo(1, "n").name
print(debug.traceback("Stack trace"))
return name
end
Tail call optimisation happens in Lua when you return with a function call:
-- Optimized
function good1()
return test()
end
-- Optimized
function good2()
return test(foo(), bar(5 + baz()))
end
-- Not optimised
function bad1()
return test() + 1
end
-- Not optimised
function bad2()
return test()[2] + foo()
end
You can refer to the following links for more information:
- Programming in Lua - 6.3: Proper Tail Calls
- What is tail call optimisation? - Stack Overflow

how to resume a coroutine in lua?

I was try to use coroutine in lua, I tried the code below, repl.it here https://repl.it/repls/WordyWonderfulVisitor and it does not print the list content in the loop.
local list = {1,2,3};
local function iter()
for i, v in ipairs(list) do
print(i, v)
coroutine.yield();
end
end
local co = coroutine.create(iter);
coroutine.resume(co);
coroutine.resume(co);
-- iter();
what's wrong with my code ?
There is nothing wrong with your code. It prints 1 1 and 2 2 as expected and the results are the same in Lua 5.1-5.4 versions.
If you want to see one more result 3 3, then you need to call resume one more time. You can also check the status of the coroutine by using coroutine.status, so after the first two executions you'll get "suspended" and after the execution is completed you'll get "dead".

Why is this function to add the contents of a table together in Lua returning nothing

I am stuck trying to make the contese of a table (all integers) add together to form one sum. I am working on a project where the end goal is a percentage. I am putting the various quantities and storing them in one table. I want to then add all of those integers in the table together to get a sum. I haven't been able to find anything in the standard Library, so I have been tyring to use this:
function sum(t)
local sum = 0
for k,v in pairs(t) do
sum = sum + v
end
return sum
However, its not giving me anything after return sum.... Any and all help would be greatly appreciated.
A more generic solution to this problem of reducing the contents of a table (in this case by summing the elements) is outlined in this answer (warning: no type checking in code sketch).
If your function is not returning at all, it is probably because you are missing an end statement in the function definition.
If your function is returning zero, it is possible that there is a problem with the table you are passing as an argument. In other words, the parameter t may be nil or an empty table. In that case, the function would return zero, the value to which your local sum is initialized.
If you add print (k,v) in the loop for debugging, you can determine whether the function has anything to add. So I would try:
local function sum ( t ) do
print( "t", t ) -- for debugging: should not be nil
local s = 0
for k,v in pairs( t ) do
print(k,v) --for debugging
s = s + v
end
return s
end
local myTestData = { 1, 2, 4, 9 }
print( sum( myTestData) )
The expected output when running this code is
t table: [some index]
1 1
2 2
3 4
4 9
16
Notice that I've changed the variable name inside the function from sum to s. It's preferable not to use the function name sum as the variable holding the sum in the function definition. The local sum in the function overrides the global one, so for example, you couldn't call sum() recursively (i.e. call sum() in the definition of sum()).

Lua table.insert does not accept a string parameter

Continuing to learn Lua.
I have wrote a function that removes the first sentence from each line and returns the result as a table of modified lines, where the first sentence was removed. Strangely, table.insert behaves weird in such function.
function mypackage.remove_first(table_of_lines)
local lns = table_of_lines
local new_lns = {}
for i=1,#lns do
table.insert(new_lns,string.gsub(lns[i],"^[^.]+. ","",1))
end
return new_lns
end
Unexpectedly, this gave me the following error.
[string "function mypackage.remove_first(table_of_lines)..."]:5: bad argument #2 to 'insert' (number expected, got string)
Why is "number expected" in the first place?
From table.insert docs
Inserts element value at position pos in list, shifting up the
elements list[pos], list[pos+1], ยทยทยท, list[#list]. The default value
for pos is #list+1, so that a call table.insert(t,x) inserts x at the
end of list t.
Nothing is said about type requirements for table.insert. Ok, I decided to modify the example.
function mypackage.remove_first(table_of_lines)
local lns = table_of_lines
local new_lns = {}
for i=1,#lns do
local nofirst = string.gsub(lns[i],"^[^.]+. ","",1)
table.insert(new_lns,nofirst)
end
return new_lns
end
And now everything works. Can you explain what is going on here?
The problem is a bit complicated. It's a collision of three factors:
string.gsub returns two parameters; the second parameter is the number of matches.
table.insert can take 3 parameters. When it is given 3 parameters, the second parameter is expected to be an integer offset defining where to insert the object.
When you do this: func1(func2()), all of the return values of func2 are passed to func1, so long as you don't pass arguments after func2 in func1's argument list. So func1(func2(), something_else) will get only 2 arguments.
Therefore, when you do table.insert(ins, string.gsub(...)), this will invoke the 3-argument version, which expects the second argument to be the index to insert the object into. Hence the problem.
If you want to ensure discarding, then you can wrap the expression in parenthesis:
table.insert(new_lns, (string.gsub(lns[i], "^[^.]+. ", "", 1)))

Lua: lua_resume and lua_yield argument purposes

What is the purpose of passing arguments to lua_resume and lua_yield?
I understand that on the first call to lua_resume the arguments are passed to the lua function that is being resumed. This makes sense. However I'd expect that all subsequent calls to lua_resume would "update" the arguments in the coroutine's function. However that's not the case.
What is the purpose of passing arguments to lua_resume for lua_yield to return? Can the lua function running under the coroutine have access to the arguments passed by lua_resume?
What Nicol said. You can still preserve the values from the first resume call if you want:
do
local firstcall
function willyield(a)
firstcall = a
while a do
print(a, firstcall)
a = coroutine.yield()
end
end
end
local coro = coroutine.create(willyield)
coroutine.resume(coro, 1)
coroutine.resume(coro, 10)
coroutine.resume(coro, 100)
coroutine.resume(coro)
will print
1 1
10 1
100 1
Lua cannot magically give the original arguments new values. They might not even be on the stack anymore, depending on optimizations. Furthermore, there's no indication where the code was when it yielded, so it may not be able to see those arguments anymore. For example, if the coroutine called a function, that new function can't see the arguments passed into the old one.
coroutine.yield() returns the arguments passed to the resume call that continues the coroutine, so that the site of the yield call can handle parameters as it so desires. It allows the code doing the resuming to communicate with the specific code doing the yielding. yield() passes its arguments as return values from resume, and resume passes its arguments as return values to yield. This sets up a pathway of communication.
You can't do that in any other way. Certainly not by modifying arguments that may not be visible from the yield site. It's simple, elegant, and makes sense.
Also, it's considered exceedingly rude to go poking at someone's values. Especially a function already in operation. Remember: arguments are just local variables filled with values. The user shouldn't expect the contents of those variables to change unless it changes them itself. They're local variables, after all. They can only be changed locally; hence the name.
A simple example:
co = coroutine.create (function (a, b)
print("First args: ", a, b)
coroutine.yield(a+10, b+10)
print("Second args: ", a, b)
coroutine.yield(a+10, b+10)
end)
print(coroutine.resume(co, 1, 2))
print(coroutine.resume(co, 3, 4))
Prints:
First args: 1 2
true 11 12
Second args: 1 2
true 11 12
Showing that the orginal values for the args a and b did not change.

Resources