"args" == ARGH! Are they _really_ good for anything?

170 views
Skip to first unread message

Scott Grosch

unread,
Jan 30, 1994, 11:33:07 PM1/30/94
to
In a previous article, j...@whisker.hubbard.ie (Jordan K. Hubbard) wrote:
>Is it just me, or is the "varargs" functionality basically pretty
>useless? (I'm not unwilling to be proven wrong here at all).

I find it quite useful.

>and say that it also uses the standard getopt(3) function for parsing

I've always found getopt() to be too limiting, and just parsed the arguments
myself. Can't you call Tcl_Split() or something like that? Maybe it's
Tcl_SplitList(), I'm still new to this language.

-- Scott

Ioi Kim Lam

unread,
Jan 31, 1994, 1:21:20 PM1/31/94
to
Jordan K. Hubbard (j...@whisker.hubbard.ie) wrote:
: Is it just me, or is the "varargs" functionality basically pretty
: useless? (I'm not unwilling to be proven wrong here at all).

: Basically, I'd like to do this:

: proc foo {joe barb args} {
: ..
: my_extended_func -hubby $joe -wifey $barb $args
: }

Change it to

eval my_extended_func -hubby $joe -wifey $barb $args

which will split the $args into its components. Perhaps you didn't
have the TCL book with you when you asked the question :)

Good luck.

Ioi.

Stephen Dedalus

unread,
Jan 31, 1994, 2:37:06 PM1/31/94
to
Jordan K. Hubbard (j...@whisker.hubbard.ie) wrote:
: Is it just me, or is the "varargs" functionality basically pretty
: useless? (I'm not unwilling to be proven wrong here at all).

: Basically, I'd like to do this:

: proc foo {joe barb args} {
: ..
: my_extended_func -hubby $joe -wifey $barb $args
: }

: [stuff deleted]

: Am I missing something really really basic?

: Jordan

Try this:


eval my_extended_func -hubby $joe -wifey $barb $args

eval basically removes one level of quotes or list braces
This will cause the args to get passed to your function as
separate arguments, not one big strings

Gregor Schmid

unread,
Jan 31, 1994, 4:16:52 AM1/31/94
to
In article <JKH.94Ja...@whisker.hubbard.ie> j...@whisker.hubbard.ie (Jordan K. Hubbard) writes:


Is it just me, or is the "varargs" functionality basically pretty
useless? (I'm not unwilling to be proven wrong here at all).

Basically, I'd like to do this:

proc foo {joe barb args} {
..
my_extended_func -hubby $joe -wifey $barb $args
}

[lots of stuff deleted]

Your proc should be:

proc foo {joe barb args} {
..

eval my_extended_func -hubby $joe -wifey $barb $args

!!!!
}

I think it's mentioned somewhere.

Ever used lisp? Same problem there...

Regards,
Greg

Peter.DeRijk

unread,
Jan 31, 1994, 4:17:59 AM1/31/94
to
Jordan K. Hubbard (j...@whisker.hubbard.ie) wrote:
: Is it just me, or is the "varargs" functionality basically pretty

: useless? (I'm not unwilling to be proven wrong here at all).
:
: Basically, I'd like to do this:
:
: proc foo {joe barb args} {
: ..
: my_extended_func -hubby $joe -wifey $barb $args
: }
:
:
: [stuff deleted]
:
: Jordan
:

You could use the eval function. This should work:


eval my_extended_func -hubby $joe -wifey $barb $args

Peter

Jordan K. Hubbard

unread,
Jan 30, 1994, 9:52:40 PM1/30/94
to
Is it just me, or is the "varargs" functionality basically pretty
useless? (I'm not unwilling to be proven wrong here at all).

Basically, I'd like to do this:

proc foo {joe barb args} {
..
my_extended_func -hubby $joe -wifey $barb $args
}

Where "my_extended_func" is a command I've added in. Let's go further


and say that it also uses the standard getopt(3) function for parsing

its args, which works just fine in all calling situations. Except
$args. Assuming that "my_extended_func" takes some extra args, like:

-fave_game golf -fave_show dallas -combined_iq 14 -pet dog

[We'll suspend disbelief for the moment and pretend that getopt()
works with arbitrarily long flag names - in my own examples I do use
getopt compliant arglists].

So OK - this is straight-forward:

my_extended_func -hubby fred -wifey wilma -fave_game "standing funny"

my_extended_func -hubby dick -wifey jane -fave_show anything -pet dog

But _this_ is not:

foo fred ethel -fave_game teevee -pet lucy

Since `args' bundles all the args together as one string and you end
up with a call actually looking like:

my_extended_func -hubby fred -wifey ethel "-fave_game teevee -pet lucy"

Even worse, if you pass it on to _anohter_ "args" taking function, the
second one will see it as a list. Naturally, this makes rational arg
parsing inside your function rather difficult, if not impossible.

I realize that this may be a fundamental design limitation of tcl,
since nothing in a command's arg list can apparently expand to
anything more than *one* argument, but it sure makes a lot of things
that would otherewise be _really simple_ a royal pain!

I'm also sorry if this sounds like I'm ragging on John for his
otherwise excellent language, I'm just very surprised that args works
the way it does and that thie limitation is not _mentioned_ anywhere.
It really chomped me good and made me look kind of foolish in front of
some folks whom I was trying to impress with the power of a TCL based
testing tool.. :-(

Eric Raible

unread,
Jan 31, 1994, 10:08:40 PM1/31/94
to

Your proc should be:

proc foo {joe barb args} {
..
eval my_extended_func -hubby $joe -wifey $barb $args
!!!!
}

I think it's mentioned somewhere.

Ever used lisp? Same problem there...

No really. Using eval is this context can have bad effects if
args contains $'s or []'s.

What is really needed here is apply, not eval.

- Eric

Scott Schwartz

unread,
Jan 31, 1994, 11:44:46 PM1/31/94
to
sch...@cs.toronto.edu (Eric Schenk) writes:
Is there something wrong with: ``eval func $lst''?

Yes, making sure $lst is a proper list. You went on to give some
examples. Do you think I'm being to picky? Eval irritates me because
it does much more work than one wants: it reparses everything, whereas
apply simply does function application. In lisp you don't need to
requote your (list of) arguments just to pass them to a function.

Eric Schenk

unread,
Feb 1, 1994, 12:54:04 AM2/1/94
to

Ah I understand your objection now. Although I'd like to clarify one
point. Write an apply function:

proc apply {args} { eval $args }

I think this behaves exactly as you describe.
You still can't write:

proc foo {arg1 arg2 args} {
apply bar -zap $arg1 -zip $arg2 $args
}

In this example "bar" will always get called with 5 arguments now.

As I recall what the lisp apply actually does (pulls down manual to
look it up) is treat the last argument as a list which must be
expanded into individual paramenters. This if I had a list "foo"
and I wanted to apply "bar" to a few parameters and then foo,
(apply bar p1 p2 foo) would not do what i want, it would rip foo apart.
Of course I could always do (bar p1 p2 foo) to get what I wanted in this case.

I think the following Tcl procedure might give you what you want.

proc apply {args} {
set len [expr [llength $args]-1]
eval [lrange $args 0 [expr {$len-1}]] [lindex $args $len]
}

Now the above procedure "foo" should behave as you might have expected.
Perhaps we should argue that "eval" should only expand its last argument.
In any case, this is a Tcl issue and not a Tk issue, and John should
ignore this discussion for the purposes planning for Tk 4.0.

-- eric

---------------------------------------------------------------------------
Eric Schenk sch...@cs.toronto.edu
Department of Computer Science, University of Toronto

Brent Welch

unread,
Feb 1, 1994, 1:06:48 AM2/1/94
to
j...@whisker.hubbard.ie (Jordan K. Hubbard) writes:

>Is it just me, or is the "varargs" functionality basically pretty
>useless? (I'm not unwilling to be proven wrong here at all).

>Basically, I'd like to do this:

>proc foo {joe barb args} {
> ..
> my_extended_func -hubby $joe -wifey $barb $args
>}

Here is something I put together that explains how I use args for
situations like this. The write-up has grown to address a bit more,
but if you are patient and read through it I think it will explain
the right approach to use $args. (Gawd, this is long... As a teaser
here is the punchline. To put this answer in context, read on...)

eval {button $myname -text $label -command [list puts stdout $label]} $args

<filed in /project/tcl/doc/README.programmer>

This is a short note to describe a deep "gotcha" with TCL and
the standard way to handle it. Up front, TCL seems pretty
straight-forward and easy to use. However, trying out some
complex things will expose you to the gotcha, which is
referred to as "quoting hell", "unexpected evaluation",
or "just what is a TCL list?". These problems, which many
very smart people have had, are indications that programmer's
mental model of the TCL evaluator is incorrect. The point
of this note is to sketch out the basic model, the gotcha,
and the right way to think (and program) around it.

THE BASIC MODEL (curtesy of John O.)

Almost all problems can be explained with three simple rules:
1. Exactly one level of substitution and/or evaluation occurs in each
pass through the Tcl interpreter, no more and no less.
2. Each character is scanned exactly once in each pass through the
interpreter.
3. Any well-formed list is also a well-formed command; if evaluated,
each element of the list will become exactly one word of the command
with no further substitutions.
For example, consider the following four one-line scripts:
set a $b
eval {set a $b}
eval "set a $b"
eval [list set a $b]
In the first script the set command passes through the interpreter
once. It is chopped into three words, "set", "a", and the value of
variable "b". No further substitutions are performed on the value
of b: spaces inside b are not treated as word breaks in the "set"
command, dollar-signs in the value of b don't cause variable
substitution, etc.

In the second script the "set" command passes through the interpreter
twice: once while parsing the "eval" command and again when "eval"
passes its argument back to the Tcl interpreter for evaluation.
However, the braces around the set command prevent the dollar-sign
from inducing variable substitution: the argument to eval is
"set a $b". So, when this command is evaluated it produces exactly
the same effect as the first script.

In the third script double quotes are used instead of braces, so
variable substitution occurs in the argument to eval, and this could
cause unwanted effects when eval evaluates its argument. For example,
if b contains the string "x y z" then the argument to eval will be
"set a x y z"; when this is evaluated as a Tcl script it results in
a "set" command with five words, which causes an error. The problem
occurs because $b is first substituted and then re-evaluated. This
double-evaluation can sometimes be used to produce interesting effects.
For example, if the value of $b were "$c", then the script would set
variable a to the value of variable c (i.e. indirection).

The fourth script is safe again. While parsing the "eval" command,
command substitution occurs, which causes the result of the "list"
command to be the second word of the "eval" command. The result of
the list command will be a proper Tcl list with three elements: "set",
"a", and the contents of variable b (all as one element). For
example, if $b is "x y z" then the result of the "list" command will
be "set a {x y z}". This is passed to "eval" as its argument, and
when eval re-evaluates it the "set" command will be well-formed:
by rule #3 above each element of the list becomes exactly one word
of the command. Thus the fourth script produces the same effect as
the first and second ones.

THE GOTCHA (observations by Brent Welch)

The basic theme to the problem is that you have an arbitrary
string and want to protect it from evaluation while passing
it around through scripts and perhaps in and out of C code you write.
The short answer is that you must use the list command to
protect the string if it originates in a TCL script, or you
must use the Tcl_Merge library procedure if the string
originiates in your C code. Also, avoid double quotes
and use list instead so you can keep a grip on things.

Now, lets rewind and start with a simple example to give some context.
We want to create a TK button that has a command associated with it.
The command will just print out the label on the button, and we'll
define a procedure to create this kind of button.
There are two opportunities for evaluation here, one when the button
is created and the command string is parsed, and again later on
when the button is clicked. Here is our TCL proc:

proc mybutton1 { parent self label } {
if {$parent == "."} {
set myname $parent$self
} else {
set myname $parent.$self
}
button $myname -text $label -command "puts stdout $label"
pack append $parent $myname {left fill}
}

The intent here is that the command associated with the button is
puts stdout $label
Now, label is only defined when creating the button, not later on when
the button is clicked. Thus we use double-quoting to group the words
in the command and to allow substitution of $label so that the button
will print the right value. However, this version will only work if
the value for label is a single list element. This is because the
double quotes around
"puts stdout $label"
allows variable substitution before grouping the words into a list.
If label had a value like "a b c", then the command string defined
for the button would be
puts stdout a b c
and pass too many arguments to the puts procedure who would complain.

THE SOLUTION

The right solution is to compose the command using the list operator.
list will preserve the list structure and protect the value that
was in $label so it will survive correctly until the button is clicked:

proc mybutton2 { parent self label } {
if {$parent == "."} {
set myname $parent$self
} else {
set myname $parent.$self
}
button $myname -text $label -command [list puts stdout $label]
pack append $parent $myname {left fill}
}

In this case, list will "do the right thing" and massage the value
of $label so that it appears as a single list element with respect
to the invocation of puts. The command string for the button will be:
puts stdout {a b c}

The second place you experience this problem is when composing
commands to be evaluated from inside C code. If the example is at
all complex, you'll want to use Tcl_Merge to build up the command
string before passing it into Tcl_Eval. Tcl_Merge takes an
argc, argv parameter set and converts it to a string while preserving
the list structure. That is, if you pass the result to Tcl_Eval,
argv[0] will be interpreted as the command name, and argv[1]
up through argv[argc-1] will be passed as the parameters to
the command. Note that Tcl_VarEval *does not* make this guarantee.
Instead, it behaves more like double-quotes by concatinating
all its arguments together and then reparsing to determine list structure.

ANOTHER GOTCHA

Now, let's extend this example with another feature that I've
found thorny. Suppose I want the caller of mybutton2 to
be able to pass in more arguments that will be passed to
the button primitive. Say they want to fiddle with the
colors of the button. Now I can add the special parameter "args"
to the end of the parameter list. When mybutton3 is called,
the variable args will be a list of all the remaining arguments.
The naive, and wrong, approach is:

proc mybutton3 { parent name label args} {
if {$parent == "."} {
set myname $parent$self
} else {
set myname $parent.$self
}
button $myname -text $label -command [list puts stdout $label] $args
pack append $parent $myname {left fill}

}

This is wrong because button doesn't want a sublist of more arguments,
it wants many arguments. So, how am I gonna stick
the value of $args onto my button command. Or, said another way,
how am I going to create the proper list structure?
It is tempting to do the following:

eval "button $myname -text $label -command [list puts stdout $label] $args"

However, this construct causes things to go through the evaluator twice,
which will lead to unexpected results. The double
quotes will allow substitution, so, again, if $label has spaces,
then the button command will not like its argument list.
Another (ugly) try:

eval "button \$myname -text \$label -command \[list puts stdout \$label\] $args"

Now $arg is the only variable that is evaluated twice, once to remove its
outermost list structure, and the second time as individual arguments to
the button command. I think a better approach is the following:

eval [concat {button $myname -text $label -command [list puts stdout $label]} $args]

In this case, $args is evaluated twice, once before the call to
concat, and a second time explicitly by calling eval. The
stuff between the curly braces is protected against substitution
on the first pass, however, (which is good), and so all
concat ends up doing is stripping off the outermost list structure
(the curly braces) from its two arguments and putting a space
between them. Another, perhaps clearer way of writing this is:

set cmd {button $myname -text $label -command [list puts stdout $label]}
eval [concat $cmd $args]

Now, with this form it is fairly clear(?) that the items in the button
command and the $args list will only be evaluated one time. Finally,
it turns out you can eliminate the explicit call to concat because
eval will do that for us if it is given multiple arguments:

set cmd {button $myname -text $label -command [list puts stdout $label]}
eval $cmd $args

Which leads us back to:

eval {button $myname -text $label -command [list puts stdout $label]} $args

OTHER HIDDEN EVALS & CONCATS

The fact that eval does a concat if given multiple arguments should
alert you. What other TCL primitives do this kind of under-the-cover
concats? The TK send command does, for example.

send $interpName $cmd
send $interpName cmd arg1 arg2 arg3

In the first example, send just does a simple eval of $cmd.
In the second case send will behave as if you had instead done:

send $interpName [concat cmd arg1 arg2 arg3]

If any of the arguments to cmd were variables that contained spaces,
or commands that returned multiple works, then cmd might not
be invoked the way you expected! Instead, I always use list to
construct the second argument to send:

send $interpName [list cmd arg1 arg2 arg3]

Other commands that do evaluation include catch and uplevel.
When I first began TCL programming with catch, I was always
putting its first argument in double quotes in order to allow
variable and command substitution. However, catch calls eval
for me, which takes care of this. Now my standard catch phrase is:

if [catch {cmd $foo [subcommand bar] constant} msg] {
puts stderr "cmd failed: $msg"
} else {
# cmd was ok
}

Or, if there are lots of commands you want to catch:

if [catch {
cmd1 ...
cmd2 ...
cmd3 ...
} msg] {
puts stderr "block failed: $msg"
} else {
# block was ok.
}

CONCLUSION

So, if you find yourself in quoting hell, fall back to the basics
and make sure you are using the list command to build up any
code fragments that will be executated later. Also, beware of
external programs like sh or csh that have different notions of
list structure. Secondly, make sure you understand the behavior
of the primitives in question. A quick phrase in a man page like
"eval concatinates is arguments" should raise a red flag.


--
----------------------------------
Brent Welch Xerox-PARC

Brent Welch

unread,
Feb 1, 1994, 1:13:42 AM2/1/94
to
der...@reks.uia.ac.be (Peter.DeRijk) writes:

OK, fine you probably blew off my previous really long reply,
but this answer is flawed. If any of $joe or $barb have special
characters (spaces, $, [ ]), this will break. A safer answer is:

eval {my_extended_func -hubby $joe -wifey $barb} $args

The quoting here ensures that $joe goes through the evaluator *once*.
$args goes through the evaluator *twice*: once before the call to eval,
and once inside eval because that's what it does, evaluate things.
In addition, eval *concatinates* its arguments if it gets more than one.
So, the above is identical in function to:

eval [concat {my_extended_func -hubby $joe -wifey $barb} $args]

In other words, you get just what you want, a single list that is
formed by splicing together {my_extended_func -hubby $joe -wifey $barb}
and whatever the value of $args is. If I were to be really pendatic,
I'd write it as:

eval [concat [list my_extended_func -hubby $joe -wifey $barb] $args ]

which is equivalent.

Scott Schwartz

unread,
Jan 31, 1994, 12:59:14 PM1/31/94
to
sch...@fb3-s7.math.TU-Berlin.DE (Gregor Schmid) writes:
eval my_extended_func -hubby $joe -wifey $barb $args
!!!!

Ever used lisp? Same problem there...

Yuck. Lisp has "apply", which is much more appropriate than eval. [
(apply func list) => (func elt_0 elt_1 elt_2 ...) ] The only way to
simulate apply in tcl is to quote all the arguments to eval, which is a
pain. Put this on my list for 4.0.

Eric Schenk

unread,
Jan 31, 1994, 3:14:54 PM1/31/94
to
schw...@roke.cse.psu.edu (Scott Schwartz) writes:

Is there something wrong with:

eval func $lst
?

This should get you "func elt_0 elt_1 ..."

Even one better, if you've got more than one list

eval func $lst1 $lst2 $lst3

is the same as

eval func [concat $lst1 $lst2 $lst3]

This seems pretty lispy to me. Of course

eval my_extended_func -hubby $joe -wifey $barb $args

doesn't quite do what you want, if either $joe or $barb are
lists you may be surprised.
The outer level list in the string

"my_extended_func -hubby $joe -wifey $barb $args"

gets stripped off by eval. The correct solution is more like:

eval my_extended_func [list -hubby $joe -wifey $barb] $args

or

eval [list my_extended_func -hubby $joe -wifey $barb] $args

whichever you prefer to write.

Hume Smith

unread,
Feb 1, 1994, 9:25:03 PM2/1/94
to
>>>>> On Mon, 31 Jan 1994 19:37:06 GMT, daed...@netcom.com (Stephen Dedalus) said:

SD> Try this:
SD> eval my_extended_func -hubby $joe -wifey $barb $args
SD> eval basically removes one level of quotes or list braces
SD> This will cause the args to get passed to your function as
SD> separate arguments, not one big strings

probably more general is
eval [list my_extended_func -hubby $joe -wifey $barb] $args
because then, if there happen to be spaces or other naughty characters
in the values of joe or barb, they'll be properly quoted by the time
eval gets ahold of them. i hope that includes $s...
--
Hume Smith 850...@dragon.acadiau.ca

Ted Dunning

unread,
Feb 2, 1994, 4:26:07 AM2/2/94
to


Ever used lisp? Same problem there...

not really.

lisp has funcall and appyly, both of whech avoid the full cost of
having eval around in a compiled environment.

but of course, in tcl, everything is a string so to the first order,
eval = funcall = apply.

John Ousterhout

unread,
Feb 3, 1994, 12:16:56 PM2/3/94
to

I just want to add my vote in support of this approach. The "eval"
command above is a short form for

eval [concat [list my_extended_func -hubby $joe -wifey $barb] $args]

Issues like this keep coming up over and over again in different contexts.
There are two things to remember:

1. "eval" does another layer of parsing. You have to use it if you
have a list and you want each element of the list to be a separate
argument to a command, rather than the whole list being one argument.

2. Any Tcl list is a valid command in the sense that if you "eval" it,
then each element of the list will become exactly one word of the command,
and you need not fear about special characters in the list elements
causing substitutions in the command. They won't. If you try this
with some simple lists you can see why: when Tcl generates a list it
quotes all of the special characters.

So, two things are happening in the above example. First, the "eval"
command causes each of the elements of the $args list to be passed to
"my_extended_func" as a separate argument. Second, the "concat" and
"list" commands are used to assemble a command safely, so that if $joe
and $barb contain special characters, they'll be passed through to
my_extended_func as-is without triggering substitutions, etc.

I think this is all explained in the Tcl book in the chapters on lists
and eval (it's certainly in the final version of the book, but I can't
remember if it's present in the draft that's FTP-able, which is several
months old).

Reply all
Reply to author
Forward
0 new messages