Область действия/контекст анонимных функций/процедур

Это один из тех вопросов для новичков, которые возникают у тех из нас, кто на самом деле не понимает, как все работает в фоновом режиме, и какое-то время продолжает думать, что мы понимаем; а затем, сталкиваясь с чем-то новым, мы понимаем, что на самом деле не поняли, что, по нашему мнению, мы сделали.

Я пытаюсь создать функцию, которая представляет собой цикл do- while (для перебора связанного списка по последовательности ключей), которая может принимать переменную, которая является телом цикла. Мой вопрос связан с передачей основного сценария в качестве переменной в другую функцию/процедуру.

В Javascript я просто передаю анонимную функцию. В Tcl область действия другая; Я не знаю, как это называется, но это не лексическая область видимости. Если я попробую анонимную процедуру, переменные, находящиеся в анонимной процедуре, но не в той, которой она передается, будут рассматриваться как новые переменные в области действия этой процедуры. Поэтому я попробовал подход uplevelupvar, показанный в тексте профессора Оустерхаута, и он работает. И, кажется, я это даже понимаю. Но, похоже, это противоположно тому, как я думал об этом в JavaScript.

Этот крошечный пример иллюстрирует то, что я имею в виду. В JS я всегда думал, что передаю анонимную функцию другой функции (вызываемой?), и она выполняется в своей области действия. Но в Tcl такое мышление, похоже, не работает; поскольку оказывается, что имя переменной передается вызываемой процедуре, чтобы предоставить переменную цикла в области вызываемой стороны обратно в контекст вызывающей процедуры, а анонимная процедура выполняется в контексте вызывающей стороны.

Мой вопрос заключается в том, что в этом примере как в Tcl, так и в JS anonyProc выполняется в контексте LoopBody или он действительно выполняется в контексте Complex, и ему доступен счетчик циклов из LoopBody? Действительно ли есть разница в том, что происходит в коде, обрабатывающем скрипт, или это просто два способа написания одного и того же?

У меня есть еще один вопрос, касающийся Tcl: можно ли это сделать с помощью анонимной процедуры и использования apply? Моя первая попытка находится в самом конце, и переменная value находится в контексте LoopBody, а не Complex.

Спасибо, что рассмотрели мой вопрос.

proc LoopBody {upvarName startKey endKey anonyProc } {
  upvar #1 $upvarName i
  for { set i $startKey } { $i < $endKey } { incr i 1 } {
    uplevel $anonyProc
  } 
}
proc Complex {} {
  foreach value {100 200 300} {
    LoopBody key 10 15\
        { 
          puts "key $key value [incr value $key]" 
        }
    puts ""
  } 
}
Complex

key 10 value 110
key 11 value 121
key 12 value 133
key 13 value 146
key 14 value 160

key 10 value 210
key 11 value 221
key 12 value 233
key 13 value 246
key 14 value 260

key 10 value 310
key 11 value 321
key 12 value 333
key 13 value 346
key 14 value 360

function loopBody (startKey, endKey, anonyProc) {
  for( let i = startKey;
        i < endKey;
        i++ ) {      
     anonyProc(i);
  }
}

function complex() {
  [100,200,300].forEach( (value,key) => {
     loopBody(
        10,
        15,
        (x) => {
          console.info(`x: ${x} value: ${value += x}`);
        }
     );
     console.info('');
  });     
}

complex();

Неудачная попытка анонимной процедуры.

proc LoopBody {startKey endKey anonyProc } {
  for { set i  $startKey } { $i < $endKey } { incr i 1 } {
    apply $anonyProc $i
  }
}

proc Complex {} {
  foreach value {100 200 300} {
    LoopBody 10 15\
        { { x } {
            puts "x $x value [incr value $x]"
          }
        }
    puts ""
  }
}

Complex

x 10 value 10
x 11 value 11
x 12 value 12
x 13 value 13
x 14 value 14

x 10 value 10
x 11 value 11
x 12 value 12
x 13 value 13
x 14 value 14

x 10 value 10
x 11 value 11
x 12 value 12
x 13 value 13
x 14 value 14

Ваш вопрос должен быть более целенаправленным. (В частности, вам следует сосредоточиться на одном языке программирования. Помимо всего остального, вы хотите, чтобы на ваш вопрос был дан ответ, поэтому сделать невозможным полный ответ для человека, не обладающего знаниями как в JavaScript, так и в TCL, — плохая идея!)

Quentin 27.09.2023 18:33

Но краткий ответ: в LoopBody вам все равно потребуется повысить уровень: uplevel apply ...

glenn jackman 27.09.2023 18:36

И вообще, вы хотите указать уровень без # -- uplevel 1 blah выполняет blah в вызывающей стороне текущего процесса; uplevel #1 blah выполняет blah в первом процессе, вызванном из кадра глобального стека (который может не быть вызывающим объектом этого процесса). Узнать текущий уровень можно командой info level. То же самое и с упваром.

glenn jackman 27.09.2023 18:38

@glennjackman Спасибо за предупреждение о #. я пробовал uplevel 1 apply ... и не может заставить его распознать значение из контекста вызывающего объекта. Я также попробовал использовать этот ответ, который похож на тот, который можно найти в тексте Ашока Надкарни на Tcl. Он работает только для тела, но когда я пытаюсь передать {params} {body} для анонимной функции, он либо рассматривает значение как новую переменную в контексте вызываемого объекта, либо выдает ошибку, что {x} не является лямбда-выражением.

Gary 27.09.2023 21:40

К сожалению, вы это сделаете upvar. Если вы знаете имя переменной, вам не нужно его передавать: upvar value value подойдет.

glenn jackman 27.09.2023 22:07

@glennjackman Понятно, спасибо. Необходимость upvar сортировки противоречит цели, потому что LoopBody не нужно ничего «знать» о переменных в аргументе, переданном в качестве тела, за исключением того, какие переменные относятся к переменным счетчиков цикла. Использование анонимной функции и передача списка имен ее переменных для использования в upvar вызываемой стороне не имеет особого смысла, если я не ошибаюсь. Если нет, то, вероятно, именно поэтому я смог найти пример в любом из текстов, на которые ссылался. Еще раз спасибо.

Gary 27.09.2023 22:27

Что ж, я думаю, что использовать «нечистый» анонимный процесс (который имеет побочный эффект изменения переменной в другой области видимости) сложно. Замыкания Javascript делают это без проблем, но Tcl — совсем другое дело.

glenn jackman 27.09.2023 22:40

@glennjackman Я не критиковал Tcl, но заметил, что передача анонимного процесса и использование upvar для его переменных, похоже, ничего не дает по сравнению с передачей тела и использованием upvar для подключения переменных из вызываемого процесса к телу (в контексте вызывающего объекта). Например, использование upvar на key только при передаче тела — это примерно то же самое, что передача процедуры, принимающей аргумент key. И вам не нужно беспокоиться о других переменных в организме. С моей ограниченной точки зрения более ясно, какие переменные находятся в каком контексте, чем при передаче функции в JS. Спасибо.

Gary 28.09.2023 03:21
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
8
99
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

В Tcl вам нужно думать о позициях в стеке кадров стека. Именно с этим работают и uplevel, и upvar. Нет захвата переменных (это можно смоделировать, но оба на самом деле делают копии — это incr не сработает — и это немного сложнее, чем то, что у вас есть здесь). Лямбда-термины (вещи, которым вы передаете apply) аналогичны процедурам, за исключением того, что способ указания их текущего пространства имен отличается; большая часть кода, который их использует, оставляет это значение по умолчанию (которое является глобальным пространством имен).

Вот версия, которая работает. (Это также тот вариант, который я бы очень рекомендовал, если вы вообще идете по пути с лямбда-термами; это достаточно эффективно.)

# it is a very good idea to ensure [uplevel] only gets one argument script
# using [list] to build it is highly recommended and very efficient 
proc LoopBody {startKey endKey anonyProc} {
    for {set i $startKey} {$i < $endKey} {incr i 1} {
        uplevel 1 [list apply $anonyProc $i]
    }
}

proc Complex {} {
    foreach value {100 200 300} {
        LoopBody 10 15 {{x} {
            upvar 1 value value
            puts "x $x value [incr value $x]"
        }}
        puts ""
    }
}

Самый простой вид построения лямбды выполняется с помощью такой процедуры:

proc lambda {arguments body} {
    set ns [uplevel 1 {namespace current}]
    return [list $arguments $body $ns]
}

Более сложная версия также будет генерировать upvar или захват (копию только для чтения), но в то же время будет менее общей. Без этой дополнительной сложности (и с lambda, как указано выше), вы получите следующее:

proc LoopBody {startKey endKey anonyProc} {
    for {set i $startKey} {$i < $endKey} {incr i 1} {
        uplevel 1 [list apply $anonyProc $i]
    }
}

proc Complex {} {
    foreach value {100 200 300} {
        LoopBody 10 15 [lambda {x} {
            upvar 1 value value
            puts "x $x value [incr value $x]"
        }]
        puts ""
    }
}

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


Очень редко использование #1 в upvar/uplevel является хорошей идеей; он привязывается к абсолютному уровню в стеке. До сопрограмм я никогда этого не видел. (Это имеет больше смысла в сопрограммах; этот уровень можно рассматривать как полезное место для хранения переменных, специфичных для сопрограммы.) В вашем коде это скорее ошибка, ожидающая своего появления.

Фактический нормальный способ написания этого кода будет следующим:

proc LoopBody {varName startKey endKey body} {
    upvar 1 $varName i
    for {set i $startKey} {$i < $endKey} {incr i 1} {
        uplevel 1 $body
    } 
}
proc Complex {} {
    foreach value {100 200 300} {
        LoopBody key 10 15 { 
            puts "key $key value [incr value $key]" 
        }
        puts ""
    } 
}

Я не обсуждаю здесь Javascript; это не тот язык, на котором я специализируюсь. Все сценарии были протестированы и признаны обеспечивающими правильный вывод в Tcl 8.6.

Donal Fellows 28.09.2023 09:08

Спасибо за ответ и примеры. Они очень полезны, и хотя я буду использовать метод передачи тела, я рад получить дополнительную информацию об аргументах лямбда. Я не студент, но я использовал тексты Оустерхаута и Надкарни, и хотя Надкарни предоставляет пример лямбда-процедуры, он не включает пространство имен. Возможно, он включает это в другое место; Я смотрел только раздел об анонимных процедурах (стр. 40 из более чем 600; так что это могло где-то там прятаться). Я добавлю эту заметку в свой экземпляр. Еще раз спасибо; ты уже несколько раз помог мне.

Gary 28.09.2023 18:40

Другие вопросы по теме