Есть ли у TCL какое-то понятие указателей на функции?
-
05-07-2019 - |
Вопрос
Работаю с TCL, и я хотел бы реализовать что-то вроде Шаблон стратегии.Я хочу передать "стратегию" для вывода на печать в функции TCL, чтобы я мог легко переключаться между печатью на экран и печатью в файл журнала.Каков наилучший способ сделать это в TCL?
Решение
TCL позволяет сохранить имя процедуры в переменной, а затем вызвать процедуру с использованием этой переменной; так
proc A { x } {
puts $x
}
set strat A
$strat Hello
вызовет proc A и распечатает Hello
Другие советы
Помимо ответа, показывающего, как назначить процедуру переменной, вы также можете передать имя процедуры в качестве аргумента другой процедуре. Вот простой пример:
proc foo { a } {
puts "a = $a"
}
proc bar { b } {
puts "b = $b"
}
proc foobar { c } {
$c 1
}
foobar foo
foobar bar
Это напечатает a = 1 и b = 1
Немного расширенный пример перечисленного выше, который может более наглядно проиллюстрировать шаблон стратегии:
proc PrintToPDF {document} {
<snip logic>
}
proc PrintToScreen {document} {
<snip logic>
}
proc PrintToPrinter {document} {
<snip logic>
}
set document "my cool formatted document here"
set printMethod "printer"
switch -- $printMethod {
"printer" {
set pMethodName "PrintToPrinter"
}
"pdf" {
set pMethodName "PrintToScreen"
}
"screen" {
set pMethodName "PrintToPDF"
}
}
$pMethodName $document
Помимо использования proc, вы можете вместо этого использовать блок кода. Есть несколько вариантов этого. первый - самый очевидный, просто eval
.
set strategy {
puts $x
}
set x "Hello"
eval $strategy
unset x
Это работает, но есть несколько недостатков. Во-первых, очевидно, что обе части кода должны сговориться с использованием общего именования аргументов. Это заменяет одну головную боль в пространстве имен (procs) другой (localals), и это, возможно, на самом деле хуже .
Менее очевидно, что eval намеренно интерпретирует свой аргумент без компиляции байт-кода. Это связано с тем, что предполагается, что eval будет вызываться с динамически генерируемыми, обычно уникальными аргументами, и компиляция в байт-код будет неэффективной, если байт-код будет использоваться только один раз, по сравнению с простой интерпретацией блока немедленно. Это легче исправить, поэтому вот идиома:
set x "Hello"
if 1 $strategy
unset x
if
, в отличие от eval
, компилирует и кэширует свой блок кода. Если блок $ стратегии
состоит только из одного или нескольких различных возможных значений, то это работает очень хорошо. Р>
Это совсем не помогает в поспешности передачи аргументов в блок с локальными переменными. Существует множество способов обойти это, например, выполнить замены таким же образом tk выполняет подстановку аргументов команды с помощью %
. Вы можете попробовать сделать некоторые хакерские вещи, используя up uplevel
или upvar
. Например, вы можете сделать это:
set strategy {
puts %x
}
if 1 [string map [list %% % %x Hello] $strategy]
Если вероятность того, что передаваемые аргументы не сильно изменятся, это хорошо с точки зрения компиляции байт-кода. С другой стороны, если аргумент часто меняется, вам следует использовать eval
вместо if 1
. В любом случае, это не намного лучше с точки зрения аргументов. Вероятность путаницы в отношении того, что передано, а что нет, меньше, потому что вы используете специальный синтаксис. Также это полезно, если вы хотите использовать подстановку переменных перед возвратом блока кода: как в set стратегии " $ localvar% x "
. Р>
К счастью, в tcl 8.5 есть настоящие анонимные функции , используя команду apply
. Первым словом в команде apply будет список аргументов и тела, как если бы эти аргументы в proc
были исключены. Остальные аргументы передаются анонимной команде в качестве аргументов немедленно. Р>
set strategy [list {x} {
puts $x
}]
apply $strategy "Hello"
Как насчет использования переменных функций?Я не очень хорошо помню TCL (прошло некоторое время ...), но, возможно, один из них сделает то, что вам нужно:
- [$var param1 param2]
- [$var] парам1 парам2
- $var param1 парам2
Если я ошибаюсь, любой волен поправить меня.
% set val 4444
4444
% set pointer val
val
% eval puts $pointer
4444
% puts [ set $pointer ]
4444
% set tmp [ set $pointer ]
4444
Чтобы выяснить, почему метод Джексона работает, помните, что в TCL everything является строкой. Работаете ли вы с литеральной строкой, функцией, переменной или чем-то еще, everything является строкой. Вы можете передать " указатель на функцию " Точно так же, как вы можете использовать «указатель данных»: просто используйте имя объекта без начального «$».
Все вышеперечисленное, хотя при переходе от пространства имен к пространству имен вы можете использовать в качестве передачи [текущее пространство имен] :: proc_name
, чтобы гарантировать, что вы не получите никаких разрывов.
Для методов OO вам необходимо следить за тем, что находится в этой теме: Передать метод определенного объекта в качестве входного аргумента в Tcl
Godspeed.