как передать переменные аргументы из одной функции в другую в tcl

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

Процедура abc1 получает два аргумента (k k) и не формирует процедуру abc1, они должны быть переданы в proc abc, где должно быть выполнено преобразование списка в массив. Преобразование списка в массив работает в proc1, т.е. abc1, но не во втором proc, т.е. abc

Полученная ошибка приведена ниже

proc abc {args} {
    puts "$args"
    array set arg $args
}

proc abc1 {args} {
    puts "$args"
    array set arg $args
    set l2 [array get arg]
    abc $l2
}

abc1 k k
abc k k

Выход:

k k
{k k}
list must have an even number of elements
    while executing
"array set arg $l1"
    (procedure "abc" line 8)
    invoked from within
"abc $l2"
    (procedure "abc1" line 5)
    invoked from within
"abc1 k k"
    (file "vfunction.tcl" line 18)

person vaichidrewar    schedule 30.12.2010    source источник
comment
Номера строк в трассировке стека неверны; это может быть из-за моего переформатирования или может быть из-за дистилляции, сделанной до публикации. В любом случае, это не очень важно для вопроса.   -  person Donal Fellows    schedule 30.12.2010


Ответы (3)


Лучшее решение: замена расширения

Правильный подход состоит в том, чтобы убедиться, что внешняя процедура (в терминах стека) правильно вызывает внутреннюю; если ожидается несколько аргументов, это то, что должно быть предоставлено. С появлением Tcl 8.5 это легко сделать с помощью небольшого языкового синтаксиса, называемого подстановкой расширения:

proc abc1 {args} {
    puts "$args"
    array set arg $args
    set l2 [array get arg]
    abc {*}$l2
    # Or combine the two lines above into: abc {*}[array get arg]
}

Все, что делает {*}, это то, что остальная часть слово должно быть разбито (с использованием правил синтаксиса списка) и использовано в качестве нескольких аргументов вместо правил Tcl по умолчанию «одно визуальное слово образует одно слово». Это идеально для этого.

Старое решение: команда Eval

Если вы по какой-то причине все еще используете старые версии Tcl (например, Tcl 8.4 или более ранние версии), то вы используете команду eval вместо приведенного выше синтаксиса:

eval abc $l2

Есть несколько более эффективных подходов к приведенному выше eval, которые вы могли видеть в старом коде; Например:

eval [linsert $l2 0 abc]
eval [list abc] [lrange $l2 0 end]
# ... etc ...

Но на самом деле все они устарели благодаря abc {*}$l2, который короче, проще в написании и быстрее. (Он просто недоступен в 8.4 или более ранних версиях, и слишком много развертываний еще предстоит обновить.) Используйте синтаксис расширения, если можете. Действительно, идиоматический код Tcl для 8.5 и более поздних версий почти никогда не нуждается в eval; степень, в которой это оказалось правдой, была даже довольно удивительной для сопровождающих языка.

person Donal Fellows    schedule 31.12.2010
comment
Другая альтернатива, возможно предпочтительнее любой из них, состоит в передаче аргументов по ссылке и использовании upvar для доступа к ним, а не {*} или eval. - person Eric Melski; 09.04.2011

Есть большая разница между

abc k k

и

abc [array get arg]

В первом случае вы передаете два аргумента, каждый из которых равен k. Во втором случае вы передаете список вещей — в вашем примере это список из двух k: k k.

Ответ Нира намеренно пытается обойти эту проблему, но лучшее решение — написать abc1 так, чтобы он правильно вызывал abc:

proc abc1 {args} {
  array set arg $args
  set l2 [array get arg]
  eval abc $l2
  # or just
  # eval abc $args
}
person Trey Jackson    schedule 30.12.2010

Когда вы передаете abc $l2, вы передаете args abc1 в качестве единственного аргумента abc. Итак, в abc args содержится список с одним элементом ({k k}).

Вы можете сделать что-то вроде этого:

proc abc {args} {    
  #the next line assumes that if $args has only a single item, then it is 
  #a list in itself. It's a risky decision to make but should work here.
  if { [llength $args] == 1 } { set args [lindex $args 0] }
  puts "$args"
  array set arg $args
}
person Nir Levy    schedule 30.12.2010
comment
это точная дословная копия ответа, который ранее был отправлен пользователем под углом (а затем впоследствии удален). Вы отвечаете с двух разных аккаунтов или скопировали чужой ответ? - person Bryan Oakley; 03.01.2011
comment
Во-первых, я сообщил об угле как о спаме, поскольку его ответ был точной дословной копией моего ответа (а не наоборот, вы можете посмотреть на время. - person Nir Levy; 06.01.2011
comment
Во-вторых, насчет того, что есть гораздо лучшие способы решить эту проблему, я не уверен: вариант eval, на мой взгляд, небезопасен, замена расширения есть только с 8.5. Если вы можете придумать лучшее решение, вы должны опубликовать его. - person Nir Levy; 06.01.2011