O TCL tem algum conceito de ponteiros de função?
-
05-07-2019 - |
Pergunta
Trabalho com TCL e eu gostaria de implementar algo como o Estratégia Padrão . Eu quero passar na "estratégia" para imprimir a saída em uma função TCL, para que eu possa alternar facilmente entre a impressão em tela e impressão para um arquivo de log. Qual é a melhor maneira de fazer isso em TCL?
Solução
TCL permite que você armazene o nome de um procedimento em uma variável e, em seguida, chamar o procedimento usando essa variável; assim
proc A { x } {
puts $x
}
set strat A
$strat Hello
irá chamar a proc A e imprimir Olá
Outras dicas
Além da resposta mostrando como você atribuir um procedimento a uma variável, você também pode passar o nome de um procedimento como um argumento para um outro procedimento. Aqui está um exemplo simples:
proc foo { a } {
puts "a = $a"
}
proc bar { b } {
puts "b = $b"
}
proc foobar { c } {
$c 1
}
foobar foo
foobar bar
Isto irá imprimir a = 1 e b = 1
Um exemplo ligeiramente ampliada do que foi listado acima, que pode ilustrar o padrão de estratégia mais claramente:
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
Além de usar uma proc, você poderia realmente usar um bloco de código em seu lugar. Existem algumas variações sobre este. primeira é a mais óbvia, apenas eval
ing-lo.
set strategy {
puts $x
}
set x "Hello"
eval $strategy
unset x
Isso funciona, mas há algumas desvantagens. Primeiro, o óbvio, os dois pedaços de código deve conspirar para usar uma nomenclatura comum para os argumentos. Isto substitui uma dor de cabeça namespace (procs) por outro (moradores), e este é, sem dúvida, na verdade, pior .
Menos óbvio é que eval deliberadamente interpreta seu argumento sem compilar bytecode. Isso ocorre porque presume-se que eval será chamado com gerado dinamicamente, argumentos geralmente únicas, e compilação para bytecode seria ineficiente se o bytecode só seria usado uma vez, em relação a apenas interpretar o bloco imediatamente. Isto é mais fácil de corrigir, então aqui está o idioma:
set x "Hello"
if 1 $strategy
unset x
if
, ao contrário eval
, faz de compilação e cache do seu bloco de código. Se o bloco $strategy
é sempre apenas um ou apenas um punhado de diferentes valores possíveis, então isso funciona muito bem.
Isso não ajuda em nada com o yuckiness de passar argumentos para o bloco com as variáveis ??locais. Há uma série de maneiras de contornar isso, como fazer substituições da mesma forma tk faz substituições no comando argumentos com %
de. Você pode tentar fazer algumas coisas hackish utilizando-se uplevel
ou upvar
. Por exemplo, você poderia fazer isso:
set strategy {
puts %x
}
if 1 [string map [list %% % %x Hello] $strategy]
Sobre a chance de que os argumentos que estão sendo passados ??não mudam muito, isso funciona bem em termos de compilação bytecode. Se, por outro lado, o argumento muda frequentemente, você deve usar eval
vez de if 1
. Esta não é muito melhor de qualquer maneira, em termos de argumentos. Há menos probabilidade de confusão sobre o que é passado eo que não é, porque você está usando uma sintaxe especial. Também esta é útil no caso de você querer usar substituição de variável antes de retornar um bloco de código: como em set strategy "$localvar %x"
.
Felizmente, tcl 8.5 tem verdadeiras funções anônimas , usando o comando apply
. A primeira palavra para o comando aplicar seria uma lista dos argumentos e corpo, como se esses argumentos para proc
tinha sido retirado. Os argumentos restantes são passados ??para o comando anônimo como argumentos imediatamente.
set strategy [list {x} {
puts $x
}]
apply $strategy "Hello"
Como sobre o uso de funções variáveis? Eu não me lembro muito TCL (ele tem sido um tempo ...) mas talvez um deles faria o que você precisa:
- [$ var param1 param2]
- [$ var] param1 param2
- $ var param1 param2
Se eu estiver errado, qualquer um é livre para me corrigir.
% set val 4444
4444
% set pointer val
val
% eval puts $$pointer
4444
% puts [ set $pointer ]
4444
% set tmp [ set $pointer ]
4444
Para esclarecer por que método funciona de Jackson, lembre-se que no TCL, tudo é uma string. Se você está trabalhando com uma string literal, uma função, uma variável, ou o que quer que seja, tudo é uma string. Você pode passar um "ponteiro de função" assim como você pode um "ponteiro de dados":. Simplesmente usar o nome do objeto sem líder "$"
Tudo o que disse acima, embora quando se deslocam de namespace ao namespace, você pode querer usar como um [namespace current ]::proc_name
passando, para garantir que você não obter qualquer quebra. Compra de métodos OO, você precisa seguir o que é neste segmento: passar por um método de um objecto específico como um argumento de entrada em Tcl
Godspeed.