Есть ли у TCL какое-то понятие указателей на функции?

StackOverflow https://stackoverflow.com/questions/253426

Вопрос

Работаю с 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.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top