views:

741

answers:

7

Working with TCL and I'd like to implement something like the Strategy Pattern. I want to pass in the "strategy" for printing output in a TCL function, so I can easily switch between printing to the screen and printing to a log file. What's the best way to do this in TCL?

+1  A: 

How about using variable functions? I don't remember much TCL (it's been a while...) but maybe one of these would do what you need:

  • [$var param1 param2]
  • [$var] param1 param2
  • $var param1 param2

If i'm wrong, anyone is free to correct me.

Tom
It's the last one that you should use.
Martin v. Löwis
+13  A: 

TCL allows you to store the name of a procedure in a variable and then call the procedure using that variable; so

proc A { x } {
   puts $x
}

set strat A
$strat Hello

will call the proc A and print out Hello

Jackson
+3  A: 

A slightly expanded example of what was listed above that might illustrate the Strategy Pattern more clearly:

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
ramanman
+4  A: 

In addition to the answer showing how you assign a procedure to a variable, you can also pass the name of a procedure as an argument to another procedure. Here's a simple example:


proc foo { a } {
   puts "a = $a"
}

proc bar { b } {
   puts "b = $b"
}

proc foobar { c } {
   $c 1
}

foobar foo
foobar bar

This will print a = 1 and b = 1

Michael Mathews
A: 

To clarify why Jackson's method works, remember that in TCL, everything is a string. Whether you are working with a literal string, a function, a variable, or whatever it may be, everything is a string. You can pass a "function pointer" just like you can a "data pointer": simply use the object's name with no leading "$".

bta
+1  A: 

Aside from using a proc, you could actually use a code block instead. There are a few variations on this. first is the most obvious, just evaling it.

set strategy {
    puts $x
}

set x "Hello"
eval $strategy
unset x

This works, but there are a few downsides. First the obvious, both pieces of code must collude to using a common naming for the arguments. This replaces one namespace headache (procs) with another (locals), and this is arguably actually worse.

Less obvious is that eval deliberately interprets its argument without compiling bytecode. This is because it is assumed that eval will be called with dynamically generated, usually unique arguments, and compiling to bytecode would be inefficient if the bytecode would only be used once, relative to just interpreting the block immediately. This is easier to fix, so here's the idiom:

set x "Hello"
if 1 $strategy
unset x

if, unlike eval, does compile and cache its code block. If the $strategy block is only ever one or just a handful of different possible values, then this works very well.

This doesn't help at all with the yuckiness of passing arguments to the block with local variables. There are a lot of ways around that, such as doing substitutions in the same way tk does substitutions on command arguments with %'s. You can try doing some hackish things using up uplevel or upvar. For example you could do this:

set strategy {
    puts %x
}

if 1 [string map [list %% % %x Hello] $strategy]

On the off chance that the arguments being passed don't change very much, this works well in terms of bytecode compilation. If on the other hand, the argument changes often, you should use eval instead of if 1. This isn't much better anyway, in terms of arguments. There's less likelyhood of confusion about what's passed and what's not, because you're using a special syntax. Also this is helpful in case you want to use variable substitution before returning a code block: as in set strategy "$localvar %x".

Fortunately, tcl 8.5 has true anonymous functions, using the apply command. The first word to the apply command would be a list of the arguments and body, as if those arguments to proc had been lifted out. The remaining arguments are passed to the anonymous command as arguments immediately.

set strategy [list {x} {
    puts $x
}]

apply $strategy "Hello"
TokenMacGuy
+1 for Tcl 8.5 information
Jonathan Leffler
It's worth pointing out that you can get this very same behavior in tcl8.0 (or later) using a proc that is shown for illustrative purposes on the man-page for apply. If you're using something even earlier then that, well, I feel for yah.
TokenMacGuy
+1  A: 
% set val 4444
4444

% set pointer val
val

% eval puts $$pointer
4444

% puts [ set $pointer ]
4444

% set tmp [ set $pointer ]
4444
name