3

How do you provide default arguments to a Tcl procedure that are evaluated at call-time?

Here's an example of what I've tried:

> tclsh
% proc test { {def [expr 4 + 3]} } { puts $def }
too many fields in argument specifier "def [expr 4 + 3]"
% proc test { {def {[expr 4 + 3]}} } {puts $def}
% test
[expr 4 + 3]
% test 5
5
% proc test { {def {[expr 4 + 3]}} } {puts [$def]}
% test
invalid command name "[expr 4 + 3]"
% proc test { {def {[expr 4 + 3]}} } {puts [eval $def]}
% test
invalid command name "7"

The example is just to simplify code. Of course in this simple example one would just use {def 7} to set the proc's default value.

However, the goal is to be able to call some more complex function that delivers a good default value whenever the procedure test is being called. So the defaults can vary.

My current solution is to default to the empty string and check that:

% proc test { {def {}} }  {  if {$def == {}} { set def [expr 4 + 3] } ; puts $def }
% test
7
% test 5
5

However I consider this not elegant enough: There ought to be a way to put declarations where they belong: In the header.

Also, possibly, the empty string might be a perfectly fine value given by a caller that is not to be replaced with the default call.

Another workaround could be to just use args as a parameter and then inspect that one. But that provides even less explicit declarative style.

Any ideas how I can incorporate the eval into the declarative proc header?

(I'm on Tcl8.4 with no way to upgrade because of use in a commercial tool environment. But for the sake of this site I'd also encourage answers for more modern tcl versions)

cfi
  • 10,915
  • 8
  • 57
  • 103
  • The solution for 8.4 is just as valid for later versions; there's not been any significant change in this specific area in the past nearly 20 years (though best-practices _have_ changed over that time). – Donal Fellows Mar 20 '12 at 11:08
  • 1
    There's been a few questions about this topic recently. See this question: http://stackoverflow.com/q/9736524/7552 – glenn jackman Mar 20 '12 at 11:27
  • @Donald: I just learned about *args unrolling in tcl 8.5 a week ago, so I was suspecting or maybe hoping that maybe the declarative syntax has changed further. – cfi Mar 20 '12 at 12:34

2 Answers2

2

What about this one (which is just slightly different from your tries):

% proc test {{def {[expr 4+3]}}} {eval puts "$def"}
% test
7
% test 5
5
bmk
  • 13,849
  • 5
  • 37
  • 46
  • Simple, elegant, works. That was a permutation I obviously did not think of. Appreciate it! Will wait a day to see if there are other ways coming in, before checking as answer. – cfi Mar 20 '12 at 10:09
  • In my more complex real case I now do an explicit eval before using the argument (as the first statement(s) of the proc) with `eval set def "$def"` – cfi Mar 20 '12 at 10:11
2

If you know that you'll only ever be passing in numbers, you can do this:

proc test {{def {4+3}}} {
    puts [expr $def]
}

But this is unsafe if you are handling general arguments. (If someone managed to pass “[exit]”, you'd notice!) Handling things safely requires more elaborate processing. If you can use a sentinel value like the empty string:

proc test {{def ""}} {
    if {[string equal $def ""]} {set def [expr 4+3]}
    puts $def
}

If you have no such sentinel possible, you have to either use the args special and do all the work yourself or you have to look at the length of the list returned by info level 0.

You can wrap proc with your own code to make this all simpler.

proc xproc {name arguments body} {
    for {set n [llength $arguments]} {[incr n -1] >= 0} {} {
        if {[llength [lindex $arguments $n]] > 1} {
            foreach {v def} [lindex $arguments $n] break
            set body "if {\[llength \[info level 0\]\] <= $n+1} {set $v \[expr $def\]};$body"
        }
    }
    proc $name $arguments $body
}

After that, you could just write this:

xproc test {{def {4+3}}} {
    puts $def
}
Donal Fellows
  • 133,037
  • 18
  • 149
  • 215
  • Love the mind boggling command mangling in xproc. Yeah, I'm stuck with arbitrary arguments. Excellent point, bringing up the security issue. I'm in a safe environment though, so it would be just an added feature to prevent people from accidentally [exit]ing or corrupting the tool's database – cfi Mar 20 '12 at 12:31
  • @cfi: Still better to avoid running through `[expr]` like that every time, as that forces recompilation of the expression each time and that's (relatively) slow. – Donal Fellows Mar 20 '12 at 14:47
  • And before you ask, `proc` itself can only use a value (i.e., a constant) for a default. You can compute the value at the time of definition of the procedure, but at that point it is constant (unless you redefine the procedure each time it changes, a trick I've seen used but not in performance-sensitive code). This stems from the fact that Tcl's values are semantically immutable; _they don't change_. This is a very subtle point. – Donal Fellows Mar 20 '12 at 14:50
  • I believe I didn't get your last comment. The constant here in this case would be the string describing some statement, wouldn't it? I'm ok with that in my use case. In my real-life case what I've used here as `[expr 4+3]` in fact is a call to a function implemented by a c-api. It doesn't even have any arguments. So, the function call is always the same. But the changing context anyway alters the data the c-api returns. The parsing of that command is ok to be slow, since the c-code's execution time is worse in O() and definitely to some great extent in the coefficients. – cfi Mar 20 '12 at 21:41
  • @cfi: Comments are not always 100% targeted at you. :-) The substance is that once you've defined a default value for a procedure argument, it can't be changed at all (not without tearing down and rebuilding the procedure from scratch, which is easy but very inefficient). This is a consequence of features of Tcl that run very deep (and the fact that `proc` is a pretty stupid command, which is usually good because it makes it preductable). That means that any runtime-determined default has to be done through code inside the procedure. – Donal Fellows Mar 21 '12 at 09:43
  • The comment about semantic immutability is one of the key distinguishing features of Tcl relative to languages like Perl, Python and Ruby (and C and Java and C++ and C# and Lisp and …). It's important because it impacts on the identity of values and on the expectations of code that works with them. But no way do I have space to write an essay on this here, and it's pretty off-topic for your question anyway. :-D – Donal Fellows Mar 21 '12 at 09:47