4

I want to replace the definition of "proc N" with a proc of the same name and calling conventions, but with a little extra error detection code.

In python I could do what I want like below, but I don't have any grasp of how namespaces and function handles work in tcl.

__orig_N = N
def N(arg1, arg2):
    if arg1 != 'GOOD VALUE':
        exit('arg1 is bad')
    return __orig_N(arg1, arg2)
Andrew Grimm
  • 78,473
  • 57
  • 200
  • 338
bukzor
  • 37,539
  • 11
  • 77
  • 111

2 Answers2

10

You can use the rename command to rename an existing proc:

rename N __orig_N
proc N {arg1 arg2} {
    if { $arg1 != "GOOD_VALUE" } {
        puts stderr "arg1 is bad"
        exit 1
    }
    return [uplevel 1 __orig_N $arg1 $arg2]
}

This is actually a little bit more sophisticated than the python original, in that the use of uplevel effectively elides the wrapper from the call stack entirely -- which may not be necessary in your case, admittedly, but it's nice to be able to do it.

Eric Melski
  • 16,432
  • 3
  • 38
  • 52
  • 1
    +1: `error "arg1 is bad"` may be a more direct translation, instead of separate `puts` and `exit` commands. – glenn jackman May 24 '11 at 18:23
  • 1
    @glenn: Perhaps, although [error] could be caught, whereas the Python exit command, afaik, unconditionally exits the interpreter, so the semantics are a bit different. – Eric Melski May 24 '11 at 18:26
  • 1
    +1: A few notes: 8.6 has `tailcall` which lets you rewrite that last line as `tailcall __orig_N $arg1 $arg2` to get even more complete elision from the call stack, and it's **very important** to not rename procedures across namespace boundaries or the resolution scope changes. (Boy, do I *hate* that particular misfeature!) – Donal Fellows May 24 '11 at 19:27
  • 1
    @Eric: The python exit() raises a SystemExit exception, which can be caught. – bukzor May 24 '11 at 21:05
  • @Donal: I'm unable to imagine what it means to "rename procedures across namespace boundaries". Can you expand this? – bukzor May 24 '11 at 21:13
  • @bukzor: thanks for the info. I've barely used Python so I didn't know. – Eric Melski May 25 '11 at 04:55
  • @bukzor: `rename ::ns1::foo ::ns2::bar`. But _don't do it_. – Donal Fellows May 25 '11 at 09:29
  • @bukzor: I wouldn't say that python `exit` is equivalent to tcl `error` in general. In this particular case, `error` may be a better match to the semantics of your python example. – Eric Melski May 26 '11 at 22:54
5

Tcl's got quite good introspection on procedures. This lets you rewrite a procedure to add in some more code:

# Assume there are no defaults; defaults make this more complicated...
proc N [info args N] [concat {
    # Use 'ne' for string comparison, '!=' for numeric comparison
    if {$arg1 ne "GOOD VALUE"} {
        error "arg1 is bad"
        # The semicolon is _important_ because of the odd semantics of [concat]
    };
} [info body N]]

OK, that's not the only way to do it – Eric's answer is closer to how I'd normally wrap a command, and it has the advantage of working with non-procedure commands as well – but this solution has the advantage of binding the code in nice and tight so that there's very little to go wrong later. It also doesn't introduce extra stack frames into any error traces, which helps keep debugging simple.

Donal Fellows
  • 133,037
  • 18
  • 149
  • 215
  • The note about Tcl's `error` versus Python's `exit` from the comments on Eric's answer applies here too, but it should be noted that using `error` (or `return -code error`) is idiomatic in Tcl whereas letting code blow the process away is not in the grounds of not being very neighborly. – Donal Fellows May 24 '11 at 19:30
  • In your second "it has the advantage of", it's unclear whether you're talking about Eric's answer or your own. – bukzor May 24 '11 at 21:07
  • one disadvantage of your approach is that it exposes you to "pollution" in your original proc from the new injected code. For example, if the stuff in the 'wrapper' creates new variables, those would be visible to the original code, which could lead to unexpected behaviors and very tricky bugs. My approach protects you from that risk. – Eric Melski May 25 '11 at 04:57
  • @Eric: For a straight argument check — what the questioner asked for — pollution is unlikely to be a problem. For more substantial processing, that'd be different. There's a few ways to handle it too (e.g., the `apply` command can limit the scope of temporaries) each with their own specific trade-offs. Of course, such problems occur in other languages too, e.g., AspectJ has the potential to make Java quite _amazingly_ obscure. – Donal Fellows May 25 '11 at 09:35
  • @bukzor: Clarified, plus explained another advantage of this version. – Donal Fellows May 25 '11 at 09:38