RU

(5.8.0) Именование переменных и их область видимости

studentik #2643 24.02.2024 07:19 6 comments 1834 views

Всем привет.
Вопрос может несколько странный, но мне до конца непонятен один нюанс при работе с локальными и обычными (глобальными) переменными, а именно - что будет, если их имена в каком-то блоке совпадут?
Например, в одной локации мы создаем переменную “count”, а потом вызываем другую локацию через gs (или, что еще интереснее - через вызов func), где объявлена локальная переменная с таким же именем “count” - что произойдет? Глобальная переменная перезапишется? Или работа из-под вызываемой локации изменится область видимости на локальную “count”?
В C/C++, например, есть оператор квалификации (::), который уточняет область видимости объекта, к которому был выполнен вызов. В QSP, понятное дело, этого нет, но что конкретно происходит в такие моменты?

Пока я стараюсь локальные переменные называть как-то уникально, чтобы они не совпадали с глобальными переменными и с локальными переменными, объявленными в вызывающих локациях, но сейчас я решил кардинально переписать несколько игровых механик, в следствии чего увеличилось количество используемых функций, которые также вызывают другие функции, и вероятность совпадения локальных переменных меня как-то начало нервировать, потому что частенько используются циклы, и, например, переменная “i” там - постоянный гость.
Пока никаких проблем не было, подозреваю, из-за того, что область видимости ограничивается конкретными блоками кода, но хотелось бы уже поставить точку в этом вопросе.

Edited at 24.02.2024 07:21 (2 years ago)
Aleks Versus Moderator 24.02.2024 18:53 (2 years ago)

studentik,
локальные переменные существуют в области видимости блоке кода, в котором они объявлены, а так же во всех вложенных блоках кода, за исключением действий. Вроде бы я это подробно расписал в разделе Локальные переменные на вики https://wiki.qsp.org/help:variables

Локальная переменная не должна затирать глобальную переменную. Это легко проверяется примерами, опубликованными на вики.

Я использую для наименования локальных переменных однобуквенные имена, или имена, оканчивающиеся символом нижнего подчёркивания, просто чтобы не путаться.

Изменил: криво написал про области видимости, поэтому исправил. В QSP области видимости ограничиваются блоками кода. К блокам кода относятся локации, действия, циклы, код, выполняемый из dynamic|dyneval.

Вики я почитываю, но видимо я просто слишком тупой, чтобы уяснить всю логику работы с первого раза, потому что я так или иначе провожу аналогию с языками программирования (и правилами области видимости в частности), поэтому подобные вопросы у меня периодически возникают - про риск перезаписи глобальной переменной, или изменение области видимости блока кода:

# location

if $args[0] = 'sort_apples':
  loop local i = 0 while i < 10 step i += 1:
    'Берем ' + i + ' яблоко '
    ...
    func('location', 'put_in_the_basket', ...)
  end

  exit
end

if $args[0] = 'put_in_the_basket':
  'Выбираем для него корзинку...'
  loop local i = 0 while i < 5 step i += 1:
    ...
    'Кладем яблоко в ' + i + ' корзинку'
  end

  exit
end

Например такой код у меня вызывает вопросы.
“если на локации объявлена локальная переменная, то её значение транслируется во все вызываемые с помощью GOSUB или FUNC локации” (вики)
Известно, что локальные переменные транслируются в func, но в данном случае мы имеем два (один из которых вызывается внутри другого) блока кода (циклы), и в обоих у нас используется “i” в качестве итератора. Оператор loop должен объявлять новую “область видимости”, но когда он это делает? После ключевого слова “loop”, или после двоеточия? В какой момент мы можем безболезненно использовать то же имя локальной переменной - в новой области видимости, без вреда для внешней?
К вики я без претензий, она написана хорошо, но ни одна вики никогда не описывает подобных тонкостей, а жаль (по крайней мере мне, слишком глупому или параноидальному). Я конечно могу проверить этот код самостоятельно и сделать выводы лично для себя, и мог бы сделать это с самого начала, и не писать этот пост, но решил что если это вопрос не на пустом месте, то это, как минимум, когда-нибудь может кому-нибудь прояснить те же вопросы, либо сподвигнуть добавить несколько слов в вики по поводу этих нюансов, как максимум.

Вполне себе #2722 25.02.2024 06:36 (1 year ago)

studentik,
Ты же работал с С++, ты в курсе как работают переопределения процедур и функций внутри объектов? Если ты не делаешь прямой отсылки к Родителю, то внутренние переменные остаются внутри функций не затрагивая внешние. Если не переписываешь, то все определение берется от Родителя, если переписал - то от данного, конкретного объекта.
Так и тут если ты описал в Функции (локации) переменную через Local, то использование “внешнего” Count на время работы функции прекращается, до момента возврата во “внешний мир”. И так бесконечно - пока хватит памяти. )))

Aleks Versus Moderator 25.02.2024 06:40 (1 year ago)

studentik,
хм. Момент с циклами мне казался интуитивно понятным банально из примера:

loop local i=0 & *p "i в цикле: " while i<6 step i+=1:

но видимо это не совсем очевидно, что раз переменная объявляется локальной в конструкции цикла после оператора loop, то она уже становится локальной для всего блока цикла и далее в while мы работаем уже с локальной переменной, равно и во всех итерациях мы работаем с локальной переменной, объявленной после loop.

Я всегда ждал вопроса о таком примере:

i=1
*pl "i до цикла <<i>>"
loop *p "i в цикле: " while i<6 step i+=1:
    local i
    *p "<<i>>, "
end
*nl "i после цикла <<i>>"

Вот тут-то с ходу не сообразишь, как это будет работать. :=D

studentik:

если на локации объявлена локальная переменная, то её значение транслируется во все вызываемые с помощью GOSUB или FUNC локации

согласен. Формулировка кривоватая. Подумаю, как её переписать точнее.

По примеру.

Ты объявил локальную переменную в блоке цикла. Её значение можно изменять в блоке цикла и во всех блоках, вызываемых из цикла. Но не во внешних блоках. В QSP нет замыканий, но механизм поиска переменных похож на такой же механизм в python. Если переменная не была определена в текущем блоке, происходит поиск в блоке выше, выше и так далее, пока не дойдёт до глобального определения.

Само собой, если ты в другом цикле объявил одноимённую локальную переменную, она не пересекается со значением переменной, объявленной в другом цикле, который не пересекается с текущим. Таким образом ты в каждом из этих циклов можешь вызывать даже одну и ту же локацию, в вызовах для разных циклов значения переменных не пересекутся. Но пересекутся в вызовах из одного цикла, если внутри вызова ты не переопределяешь переменную.

Если всё равно боязно, используй в качестве локальных переменных - args. Этот массив автоматически полностью локален для каждого блока кода (есть одно исключение). Тут совершенно нет замыканий, и поиск в блоках выше не производится. То есть для каждого вызова каждого блока кода существует собственный уникальный массив args.

Значения массива args, определённые для текущей локации (то есть локации, на которую игрок перешёл с помощью goto или xgoto), транслируются и в действия, созданные на этой локации. Поэтому с ними можно работать, как с глобальными. Но, если перезайти на локацию, это будет уже новый массив args.
Локальные переменные такого поведения не показывают. Они существуют только в момент выполнения кода локации, и потому из действий, созданных на локации доступны только глобальные переменные, но не одноимённые локальные, определённые на этой локации.

Области видимости для result не отличаются от областей видимости для args.

Надеюсь, объяснил понятно, и не запутал.

Aleks Versus Moderator 25.02.2024 07:01 (1 year ago)

Ещё следует учитывать, что QSP - интерпретируемый язык. То есть ни один блок кода заранее не создаётся, а создаются они по ходу выполнения кода. Это может сбивать с толку, если очень много пользуешься jump. Пример:

loop local i = 6 while i < 10 step i += 1:
    'Берем ' + i + ' яблоко '
    !...
    !func('location', 'put_in_the_basket', ...)
    if i = 8: jump 'метка'
  end



  'Выбираем для него корзинку...'
  loop local i = 3 while i < 5 step i += 1:
    :метка
    !...
    'Кладем яблоко в ' + i + ' корзинку'
  end

Если всё понял из предыдущего сообщения, легко объяснишь поведение кода.

Трансляции локальных переменных во вложенные блоки кода очень выручают, на самом деле. Раньше, когда мне нужно было поработать с массивом внутри локации, но чтобы он не пересёкся случайно с одноимённым массивом в другой локации, мне приходилось перед каждой точкой выхода (exit) его уничтожать, или вместо нескольких точек выхода делать jump к концу локации. Теперь я просто объявляю локальный массив в начале локации, и он сам уничтожается при любом выходе с локации. И это не считая того, что надо было следить, чтоб имена подобных массивов не пересекались в процедурах, которые могут вызываться одна из другой.

Ну и сейчас колбэки гораздо легче писать при возможности транслировать значения. Да, это не полноценные колбэки, и тем не менее.

Вполне себе, Aleks Versus,
Скажем так, у меня есть некоторый опыт работы с энным количеством интерпретаторов, у которых так или иначе были индивидуальные нюансы (или банально, недочеты, но суть того не меняет), равно как и взгляды их разработчиков на порядок работы отдельных конструкций кода. Душой кривить не буду - у меня у самого есть собственный интерпретатор скриптов, написанный для личного пользования, который я использую буквально ежедневно, и там тоже есть пара своих нюансов, вынужденно сделанных исходя из соображений оптимизации и читаемости. Поэтому данную тему прошу рассматривать как продукт профдеформации. :rolleyes:

Далеко ходить не буду, и касаемо:

Aleks Versus:

Момент с циклами мне казался интуитивно понятным банально из примера

отвечу, что на моей памяти были и такие, по аналогии с QSP, где все, что между loop и двоеточием - определилось бы за этим блоком и лежало бы “уровнем выше”, перезаписав одноименные переменные. Поэтому для меня подобные моменты без объяснения звучат как “а что если нет?”.

Aleks Versus,
за объяснение спасибо, лишним точно не будет :)
Что до массивов, кстати, то если бы была возможность модифицировать значение многомерного массива копирования-удаления и прочих без костылей, как это делает arritem для получения значения, то было бы в разы все проще… Но это уже для отдельной темы разговор - эта тема по сути исчерпана, т.к. ответы на свои вопросы в ней я получил. Надеюсь, что кому-то однажды она тоже поможет :)

Log in or Register to post comments.