2

I want to do something like this in principle:

#grab some value from outer source (i.e. file or list defined by another programer)
set original_proc_name foo

#define function with a specific name using the string from that outer source
proc "helper_[set original_proc_name]" {val} {
    #I need to use that string inside the body of the new proc
    return [[set original_proc_name] $val]
}

I basically need to magically control the proc body as if it was a [list . . .]

Is there any way to dynamically define the proc body during runtime?

I wish to do this because I am required to define a bunch of procs used for testing that will basically simply call a bunch of procs written by other programmers with specific parameters.

Why do I define new procs? Because that is what the regression we have setup expects to see (names of procs to run).

AturSams
  • 7,568
  • 18
  • 64
  • 98
  • Not sure what you really want to do. It is surely possible, but please try to explain what you really want to achieve with this. – schlenk Feb 12 '14 at 19:09
  • Don't put code in comments please. The indent gets lost and becomes confusing. Put it in your question please. – Jerry Feb 12 '14 at 19:16

4 Answers4

4

In Tcl, nothing is especially sacred. You can use anything in the language with pretty much anything else. In particular, you can use any string-generating command (all of them actually, though some only produce the empty string) to produce a body of a procedure; proc is just an ordinary Tcl command (that happens to make other Tcl commands, though there are others that do that too).

In your specific case, you might be better off using a different approach though.

set original_proc_name foo

interp alias {} helper_$original_proc_name {} apply {{cmd val} {
    $cmd $val
}} $original_proc_name

OK, this looks more than a bit magical. It's not really. Let's work from the inside out.

apply is a command that effectively works like a nameless procedure. It's first argument is a list (usually with two elements) that defines the arguments to the “procedure-like” thing and the body. It's subsequent arguments are the actual arguments. Thus,

apply {{a b} {
    puts "a=$a and b=$b"
}} 123 456

will print a=123 and b=456. We're using that with a specific body that takes two arguments and applies one to the other; the first argument is a command name (presumably) and the second is the value to send to that command. So we go:

apply {{cmd val} {
    $cmd $val
}} puts "Hi"

and we get a very mundane piece of code indeed. Where's the benefit?

Well, we're using interp alias here. This particular form:

interp alias {} foo {} bar boo

makes it so that when you use foo as a command, Tcl hands off the invokation to bar boo, appending on any extra arguments to foo. In a simple case:

interp alias {} writeError {} puts stderr

gives us a command that will write messages to stderr like this:

writeError "GRUMPY CAT WANTS NO PART OF THIS!"

OK, so we're putting these two things together.

interp alias {} helper_$original_proc_name {} apply {{cmd val} {
    $cmd $val
}} $original_proc_name

Here, the command alias we're making is generated by string substitution, and we're doing a partial application of the inner simple invoker so that it is locked to the original command name. The result is that we've made helper_foo be a command that only ever takes one argument, and that passes that argument on to the original foo for handling.

And all without complicated string substitution.

This case is perhaps a little overkill, but for more complicated tasks it's much simpler than generating correct scripts. It's also slightly more overkill than this, which I'd use in my own code:

interp alias {} helper_$original_proc_name {} $original_proc_name

But that's got subtly different semantics.


In Tcl 8.6 you've also got tailcall. That can make things even more “interesting”. With great power comes great opportunity for irresponsibility…

Donal Fellows
  • 133,037
  • 18
  • 149
  • 215
1

Specific example (to answer the question) from Glenn Jackman:

proc helper_$original_proc_name {val} "return \[$original_proc_name \$val]"

I managed to accomplish this with double quotes:

set some_val 7
set new_proc_body "puts $some_val \nputs \[expr $some_val * 2\]"
proc bar {} $new_proc_body
bar

Here is another example to demonstrate the principle:

set lst {dog strong bong}
foreach iter $lst {proc "super_[set iter]" {} "puts \"my name is $iter\" \nputs \"Yay $iter is great!\""}
super_dog
AturSams
  • 7,568
  • 18
  • 64
  • 98
  • 1
    Yep. Translating the code in the question: `proc helper_$original_proc_name {val} "return \[$original_proc_name \$val]"` – glenn jackman Feb 12 '14 at 19:46
  • 1
    To improve readability, you can use a multi-line string with double quotes just like you can with braces -- don't need embedded `\n`, use actual newlines. – glenn jackman Feb 12 '14 at 19:49
1

You've answered your own question, presumably to your satisfaction. If you intend to explore writing Tcl that writes Tcl further, there are still a couple of things that might be helpful to think about. One thing is to remember that names are rarely special, e.g. and for instance, there is no need to be afraid of the name list:

set list {dog strong bong}

As is sometimes pointed out, the invocation

set set set

is perfectly valid Tcl and doesn't break anything.

Another thing is that the body of a proc is a list (as well as a string, and a list of strings, and a list of lists), and can be treated as such. So this works, and lets you avoid a lot of backslashing:

foreach iter $list {
    proc super_$iter {} [join [list \
        [list puts "my name is $iter"] \
        [list puts "Yay $iter is great!"]] \n]
}

another way:

foreach iter $list {
    lappend body \
        [list puts "my name is $iter"] \
        [list puts "Yay $iter is great!"]
    proc super_$iter {} [join $body \n]
    set body {}
}

yet another:

set outputs {{my name is $iter} {Yay $iter is great!}}
foreach iter $list {
    set body [lmap output $outputs { list puts [subst $output] }]
    proc super_$iter {} [join $body \n]
}

In Tcl, code is data, and data can be code. Anything you can do with a string or a list you can do with a proc body.

Peter Lewerin
  • 13,140
  • 1
  • 24
  • 27
  • I tell that to all my colleagues that are new to tcl. They sometimes find the idea that any string you can create on the fly is a perfectly legal [variable name / proc name / piece of code] puzzling; But I didn't know you could name variables with the proc names. +1 – AturSams Feb 12 '14 at 20:47
0

My approach would be to simply retrieve the existing proc body, modify the script and redefine the proc.

proc myProc {} {
    puts ABC
}

Ouputs ABC

set body [info body myProc]
append body "puts DEF"

proc myProc {} $body 

myProc

Outputs ABC DEF