Это один из тех вопросов для новичков, которые возникают у тех из нас, кто на самом деле не понимает, как все работает в фоновом режиме, и какое-то время продолжает думать, что мы понимаем; а затем, сталкиваясь с чем-то новым, мы понимаем, что на самом деле не поняли, что, по нашему мнению, мы сделали.
Я пытаюсь создать функцию, которая представляет собой цикл do- while (для перебора связанного списка по последовательности ключей), которая может принимать переменную, которая является телом цикла. Мой вопрос связан с передачей основного сценария в качестве переменной в другую функцию/процедуру.
В Javascript я просто передаю анонимную функцию. В Tcl область действия другая; Я не знаю, как это называется, но это не лексическая область видимости. Если я попробую анонимную процедуру, переменные, находящиеся в анонимной процедуре, но не в той, которой она передается, будут рассматриваться как новые переменные в области действия этой процедуры. Поэтому я попробовал подход uplevel
upvar
, показанный в тексте профессора Оустерхаута, и он работает. И, кажется, я это даже понимаю. Но, похоже, это противоположно тому, как я думал об этом в 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
Но краткий ответ: в LoopBody вам все равно потребуется повысить уровень: uplevel apply ...
И вообще, вы хотите указать уровень без #
-- uplevel 1 blah
выполняет blah в вызывающей стороне текущего процесса; uplevel #1 blah
выполняет blah в первом процессе, вызванном из кадра глобального стека (который может не быть вызывающим объектом этого процесса). Узнать текущий уровень можно командой info level
. То же самое и с упваром.
@glennjackman Спасибо за предупреждение о #.
я пробовал uplevel 1 apply ..
. и не может заставить его распознать значение из контекста вызывающего объекта. Я также попробовал использовать этот ответ, который похож на тот, который можно найти в тексте Ашока Надкарни на Tcl. Он работает только для тела, но когда я пытаюсь передать {params} {body} для анонимной функции, он либо рассматривает значение как новую переменную в контексте вызываемого объекта, либо выдает ошибку, что {x} не является лямбда-выражением.
К сожалению, вы это сделаете upvar
. Если вы знаете имя переменной, вам не нужно его передавать: upvar value value
подойдет.
@glennjackman Понятно, спасибо. Необходимость upvar
сортировки противоречит цели, потому что LoopBody
не нужно ничего «знать» о переменных в аргументе, переданном в качестве тела, за исключением того, какие переменные относятся к переменным счетчиков цикла. Использование анонимной функции и передача списка имен ее переменных для использования в upvar вызываемой стороне не имеет особого смысла, если я не ошибаюсь. Если нет, то, вероятно, именно поэтому я смог найти пример в любом из текстов, на которые ссылался. Еще раз спасибо.
Что ж, я думаю, что использовать «нечистый» анонимный процесс (который имеет побочный эффект изменения переменной в другой области видимости) сложно. Замыкания Javascript делают это без проблем, но Tcl — совсем другое дело.
@glennjackman Я не критиковал Tcl, но заметил, что передача анонимного процесса и использование upvar для его переменных, похоже, ничего не дает по сравнению с передачей тела и использованием upvar для подключения переменных из вызываемого процесса к телу (в контексте вызывающего объекта). Например, использование upvar
на key
только при передаче тела — это примерно то же самое, что передача процедуры, принимающей аргумент key
. И вам не нужно беспокоиться о других переменных в организме. С моей ограниченной точки зрения более ясно, какие переменные находятся в каком контексте, чем при передаче функции в JS. Спасибо.
В 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.
Спасибо за ответ и примеры. Они очень полезны, и хотя я буду использовать метод передачи тела, я рад получить дополнительную информацию об аргументах лямбда. Я не студент, но я использовал тексты Оустерхаута и Надкарни, и хотя Надкарни предоставляет пример лямбда-процедуры, он не включает пространство имен. Возможно, он включает это в другое место; Я смотрел только раздел об анонимных процедурах (стр. 40 из более чем 600; так что это могло где-то там прятаться). Я добавлю эту заметку в свой экземпляр. Еще раз спасибо; ты уже несколько раз помог мне.
Ваш вопрос должен быть более целенаправленным. (В частности, вам следует сосредоточиться на одном языке программирования. Помимо всего остального, вы хотите, чтобы на ваш вопрос был дан ответ, поэтому сделать невозможным полный ответ для человека, не обладающего знаниями как в JavaScript, так и в TCL, — плохая идея!)