[SOLVED] When are functions assigned to a variable actually computed?

Issue

I’m doing some troubleshooting on a Lua function I wrote yesterday as part of a game development effort using the Love2D API. The function is designed to handle mob entities stepping around an obstruction in a tile-based roguelike type system with PG terrain. Code is below, though I’m not primarily looking for syntax help here, it’s just for context. The troubleshooting raised an interesting question in my mind that I couldn’t find the answer to in Lua’s documentation.

For the curious, this particular function checks "left" and "right" of an obstruction for any given direction in a set of 8 directions, sees whether the square in that direction contains walkable terrain, and then walks onto it if so. If both directions are clear, it flips a coin. If both are blocked, it gives up (for now, for testing purposes).

I’m defining this inside of a (confirmed working) Entity class, hence all the "self" sugar. Move, canMove, and Wander are all also confirmed working methods of Entity.

ObstructionHandler = function(self, x_direction, y_direction)
    local direction = {
        x = x_direction,
        y = y_direction
    }
    local direction_list = {{0, -1}, {1, -1}, {1, 0}, {1, 1}, {0, 1}, {-1, 1}, {-1, 0}, {-1, -1}}

    local check1 = nil
    local check2 = nil
    local move1 = nil
    local move2 = nil
        
    for i = 1, #direction_list do
        if direction.x == direction_list[i][1] and direction.y == direction_list[i][2] then
            if i == 1 then 
                check1 = self:canMove(direction_list[#direction_list][1], direction_list[#direction_list][2])
                move1 = self:Move(direction_list[#direction_list][1], direction_list[#direction_list][2])
            else
                check1 = self:canMove(direction_list[i - 1][1], direction_list[i - 1][2])
                move1 = self:Move(direction_list[i - 1][1], direction_list[i - 1][2])
            end
            
            if i == #direction_list then
                check2 = self:canMove(direction_list[1][1], direction_list[1][2])
                move2 = self:Move(direction_list[1][1], direction_list[1][2])
            else    
                check2 = self:canMove(direction_list[i + 1][1], direction_list[i + 1][2])
                move2 = self:Move(direction_list[i + 1][1], direction_list[i + 1][2])
            end
                if check1 and check2 then
                    if math.random(0,1) <= 0.5 then
                        move1
                    else -- this is where the error pops
                        move2
                    end
                elseif check1 then
                    move1  
                elseif check2 then
                    move2
                else
                    self.boredom = self.boredom + 1
                    self:Wander()
                end
            end 
    end
end

The error I receive is that an equal sign is expected near "else". I’ve seen these sorts of things pop up before when I call a function improperly, or when there’s a failure to close a parenthesis somewhere, whatever. After checking for that sort of thing, I tried moving the "self:" sugar from the declaration to the place where I actually call the functions, i.e.:

...
            if i == #direction_list then
                check2 = canMove(direction_list[1][1], direction_list[1][2])
                move2 = Move(direction_list[1][1], direction_list[1][2])
            else    
                check2 = canMove(direction_list[i + 1][1], direction_list[i + 1][2])
                move2 = Move(direction_list[i + 1][1], direction_list[i + 1][2])
            end
                if self:check1 and self:check2 then -- this is where the error pops in this version
                    if math.random(0,1) <= 0.5 then
                        self:move1
                    else
                        self:move2
                    end
                elseif self:check1 then
                    self:move1
                elseif self:check2 then
                    self:move2
                else
                    self.boredom = self.boredom + 1
                    self:Wander()
                end
...

What I found really interesting in doing this is that it changed the error. When running it like this, Love returns the error "function arguments expected near ‘and’". I double-checked canMove and Move, and they are getting all the arguments they need, here. However, this got me thinking: when is Lua (and/or Love) actually computing the results of a function when that function is assigned to a variable?

If I say something like a = f(x), is it running f(x) there and then, and assigning the output to a at that time? Or does it wait until I later call on a to run f(x)? Or is it both, like is it redoing that work if I start with a declaration and then later call on it?

If any intrepid souls want to help fix my code, that’s awesome, but I’ll figure that out myself in time; what I’m primarily interested in is this hair-splitting thing about assignation and computation, because now that it’s on my mind, it’s driving me nuts not knowing.

Solution

move is not a valid Lua statement. You cannot just write an identifier on its own. You need to combine it with other things.

Adding self: doesn’t change that. It merely replaces the local move1 by a table element. Still this is an invalid expression.

The only thing that changes is that Lua now expects arguments as the colon syntax is only used in combination with functions.

If move1 is a function value you can call

move1()

or if it was a method of the table self you could call

self:move1()

But just writing move1 is syntactically incorrect.
Same for move2.

If I say something like a = f(x), is it running f(x) there and then,
and assigning the output to a at that time? Or does it wait until I
later call on a to run f(x)? Or is it both, like is it redoing that
work if I start with a declaration and then later call on it?

Function calls happen at runtime. So does the function definition which is also just an assignment. Your program is executed line by line from top to bottom.

Answered By – Piglet

Answer Checked By – Clifford M. (BugsFixing Volunteer)

Leave a Reply

Your email address will not be published.