Love2D: Область видимости и локальные переменные

Руководство по Lua 5.1
До этого момента мы просто назначали значения переменным и могли получить эти значения, обратившись по имени переменной в скрипте. Это довольно удобно для маленьких проектов, но теперь, когда вы ознакомились с функциями, это может создать некоторые проблемы: а что будет, если функции будут использовать переменные с одинаковыми именами для хранения временных значений? Они просто будут переписывать друг друга, а скрипт превратится в невозможное для отладки месиво. Решение есть: использовать локальные переменные.

Создание локальной переменной

Для создания локальной переменной укажите ключевое слово local перед именем:
    local a = 5
    print(a)
Для изменения значения локальной переменной не требуется указывать ключевое слово local
    local a = 5
    a = 6 
Локальные переменные существуют только в пределах блоков, в которых были созданы, за их пределами они больше не существуют:
    local a = 5
    print(a) --> 5

    do
      local a = 6 -- создаём переменную а внутри блока do
      print(a) --> 6
    end

    print(a) --> 5
Граница, где существует локальная переменная, где она "видима", называется областью видимости.
    function bar()
      print(x) --> nil
      local x = 6
      print(x) --> 6
    end

    function foo()
      local x = 5
      print(x) --> 5
      bar()
      print(x) --> 5
    end

    foo()
Как вы видите, локальные переменные видимы только внутри блоков, где они были объявлены. Пусть даже локальные переменные имеют одинаковое название, они не зависят друг от друга.

Локальные функции - синтаксический сахар

    local function f() end

    -- равно

    local f
    f = function() end

    -- но не равно

    local f = function() end
Разница между последними двумя вариантами весьма велика : первые два варианта создадут локальные переменные, а третий создаст глобальную.

Замыкания

Функции могут использовать локальные переменные, созданные вне их пределов . Такие функции называются замыканиями.
Начнём с простого примера. Допустим, у вас есть список имён учеников и таблица, в которой соотносятся имена с классов учеников. Вы хотите отсортировать список имён в соответствии с классами (от большего к меньшему). Можно сделать так:
    names = {"Peter", "Paul", "Mary"}
    grades = {Mary = 10, Paul = 7, Peter = 8}
    table.sort(names, function (n1, n2)
      return grades[n1] > grades[n2]    -- сравниваем классы
    end)
А теперь, допустим, вы хотите создать функцию для этой задачи:
function sortbygrade (names, grades)
      table.sort(names, function (n1, n2)
        return grades[n1] > grades[n2]    -- compare the grades
      end)
    end
Интересно здесь то, что безымянная функция, переданная в функцию sort, получается доступ к аргументу grades, являющийся локальным для функции sortbygrade. Внутри безымянной функции grades не является ни локальной, ни глобальной переменной. Это, так называемая, внешняя локальная переменная (Далее ВЛП)
Рассмотрим следующий код:
 function newCounter ()
      local i = 0
      return function ()   -- безымянная функция
               i = i + 1
               return i
             end
    end
    
    c1 = newCounter()
    print(c1())  --> 1
    print(c1())  --> 2
Теперь безымянная функция использует ВЛП i для счёта. Однако, когда мы вызываем безымянную функцию, i уже вне области видимости, т.к. newCounter уже вернула безымянную функцию. Тем не менее Lua справляется с такими ситуациями, используя замыкания.
Если мы снова вызовем newCounter, создастся новая локальная переменная i и, соответственно, новое замыкание:
 c2 = newCounter()
    print(c2())  --> 1
    print(c1())  --> 3
    print(c2())  --> 2
Также эффективная связка замыкания+функции обратного вызова.
Рассмотрим такую ситуацию: вы делаете интерфейс для цифрового калькулятора, и вы хотите сделать несколько разных кнопок, выполняющие немного разные функции при нажатии. Можно создать эти функции так:
function digitButton (digit)
      return Button{ label = digit,
                     action = function ()
                                add_to_display(digit)
                              end
                   }
    end
В этом примере мы расскатриваеи Button как инструментарий функции для создания новой кнопки.
label обозначение кнопки; action функция обратного вызова при нажатии. (В действительности это замыкание, т.к. имеет доступ к ВЛП digit). Функция обратного вызова может быть вызвана и через некоторое время после того, как digitButton выполнит свою задачу а локальная переменная digit выйдет из области видимости. но всё равно она будет иметь доступ к этой переменной.

=== Когда стоит использовать локальные переменные? ===
Основное правило: всегда используйте локальные переменные, за исключением тех случаев, где необходимо, что бы они были глобальными.
Так как можно забыть указать ключевое слово local и сам Lua никак не предупреждает об этом, возможно появления багов. Одним из решений данной проблемы является использование скрипта
"strict.lua" (написан ниже). Скопируйте скрипт себе в проект и укажите в главном файле строку:
require("strict")
    -- strict.lua
    -- отслеживает использование необъявленных глобальных переменных
    -- Все глобальные переменные должны быть инициализированы во время объявления
    -- (можно даже установить значение nil)

    local mt = getmetatable(_G)
    if mt == nil then
      mt = {}
      setmetatable(_G, mt)
    end

    __STRICT = true
    mt.__declared = {}

    mt.__newindex = function (t, n, v)
      if __STRICT and not mt.__declared[n] then
        local w = debug.getinfo(2, "S").what
        if w ~= "main" and w ~= "C" then
          error("assign to undeclared variable '"..n.."'", 2)
        end
        mt.__declared[n] = true
      end
      rawset(t, n, v)
    end
      
    mt.__index = function (t, n)
      if not mt.__declared[n] and debug.getinfo(2, "S").what ~= "C" then
        error("variable '"..n.."' is not declared", 2)
      end
      return rawget(t, n)
    end

    function global(...)
       for _, v in ipairs{...} do mt.__declared[v] = true end
    end
данная статья является вольным переводом с сайта lua-users.org
с возможными дополнениями и изменениями

Просмотров: 2 915

prog #1 - 6 лет назад 0
Название статьи поправь - "области видимости", а не просто "области". Еще лучше - "локальные переменные" т.к. о самих областях видимости сказано мало.
Замыкания как-то не очень внятно описаны - им можно целую статью посвятить, а тут пара абзацев и не совсем точно. О замыканиях на lua.org:
Андреич #2 - 6 лет назад -1
prog:
Название статьи поправь - "области видимости", а не просто "области". Еще лучше - "локальные переменные" т.к. о самих областях видимости сказано мало.
Замыкания как-то не очень внятно описаны - им можно целую статью посвятить, а тут пара абзацев и не совсем точно. О замыканиях на lua.org:
ок, позже поправлю и добавлю больше инфы о замыканиях...