Version 90 of proc

Updated 2021-06-22 17:25:41 by pooryorick

proc , a built-in procedure, creates a new procedure.

Synopsis

proc name arguments body

Documentation

official reference

See Also

apply
command
corp
Generate a script to reproduce a procedure.
Creating Commands
Enumerates and compares the procedures that create procedures.
lego proc manipulation
A sort of procedure templating facility.
Local procedures
A discussion on limiting the lifespan of a procedure inside another procedure.
lproc
Like proc but the body is a list of lists.
overloading proc
Guarded proc
Lambda in Tcl
Unnamed/anonymous procedures.
Steps towards functional programming
Jimulation
Overloads proc to accept a list of static variables.
Printing proc sequence
A mechanism to trace procedure calls.
Procedure calling timed
Timing procedure calls.
proc alias
How to make an alias for a procedure
Procs as data structures
Procs as objects
Runtime creation of procs
scope
Simple proc tracing
mkproc
If we had no proc
saving all procedures
Wrapping a procedure
Wrap a procedure and call the original, which is stored in $next, at any time
wrapping commands
Various approaches to wrapping procedures.

Description

proc creates a new procedure named name, replacing any existing procedure having the same name. When name is not absolute, i.e. when the first namespace part is not the empty string, the new procedure is created in the current namespace. If name includes any namespace qualifiers, it is resolved relative to the current namespace and the procedure is created in the corresponding namespace.

arguments is a list of zero or more argument specifications. Each specification where the first item is the name of the variable to store the corresponding argument to, and the second item, if provided, is the default value of the variable. Any argument that follows an argument having a default value must itself have a default value.

When the new procedure is called, each argument to the call is stored in the specified variable at the new level for the call, and body is evaluated at that level. global, upvar, namespace upvar, and variable can be used to link local variables to namespace variables and variables at higher levels. Similarly, uplevel and namespace eval can be used to evaluate scripts in namespaces or at other levels. Procedure names in body are resolved as described in name resolution.

Example:

proc myproc {arg1 arg2} {
    # do something here, e.g.
    puts [list arg1 $arg1 arg2 $arg2]
}

If the last argument specification for a procedure is args, then the procedure accepts a variable number of arguments, which are stored as a list in $args:

proc myvarproc {foo bar args} {
    puts "<$foo> <$bar> <$args>"
}

myvarproc a b c d e
# => <a> <b> <c d e>
myvarproc a b
# => <a> <b> <>

When the command is evaluated all additional words are added to a list which is then assigned to $args. If there are no additional words, $args is empty. If args is the only argument, $args is a list of all the words after the procedure name. Thus, list can be implemented as:

proc List args {set args}

The value of a procedure is the value of the last command evaluated in body . return can be used to end a procedure at an earlier point in body.

When a procedure is overwritten no warning is given. To guard against such an event, see overloading proc with a Guarded proc.

Because body is a script to be evaluated, a procedure created by proc does not extend Tcl.

Procedure Results

Silas 2005-08-18: I think the best way to return multiple values is to use a list. For example:

proc v {} {
    set value1 somevalue
    set value2 anothervalue
    return [list $value1 $value2]
}

arjen told me would be a good idea to use upvar. See (I haven't tested it):

proc v {name1 name2} {
    upvar 1 $name1 n1
    upvar 1 $name2 n2
  
    set n1 1
    set n2 2
}

v N1 N2
puts "$N1 $N2"

RS: The first approach, returning a list of the results, is "cleaner" in the functional programming sense. The upvar approach creates side effects as it changes the value of variables.

Lars H: In real life, which approach is preferable varies quite a lot. There are on the whole at least three possibilities to consider:

  • Return the list of the results.
  • Return one result as the value of the procedure, and the others using upvared variables.
  • Return all results in variables.

Taking apart a list of values returned is sometimes annoyingly cumbersome, but in other cases that list is a good thing to have. Usually it all depends on what you're going to do with the results once you've got them.

One thing that is fairly easy to do using upvared variables is to provide optional results -- provide an optional argument for the name of a variable in which to pass back a result, but if such an argument is not given then don't pass the result back (perhaps avoid computing it altogether).

RS: Taking apart a list is pretty easy with the foreach ... break idiom :)

foreach {first second third} [makeThreeResults $input] break

Larry Smith: I still think let is more readable:

let a b c @= 1 2 3

DKF: Use lassign from 8.5 onwards.

lassign [makeThreeResults $input] first second third

A Procedure Always Has a Name

When a procedure is created from within the body of another procedure, it is bound to the given name in a namespace, remains, even after evaluation at the current level has completed, until it is deleted or its namespace is deleted. There is no way to create a "local" procedure at the current level.

namespace eval foo {
    proc one {} {
        puts -nonewline {Eat }
        proc two {} {
            puts -nonewline {more }
            proc three {} {
                puts chicken.
            }
        }
    }
}

foo::one
foo::two
foo::three

Default Values

Selected Topics in Tcl/Tk , Changhai Lu
A discussion of the default argument feature.

schlenk 2004-08-03: Here's a little procedure to check if defaults were used or actual arguments were present, this can be handy if a default value is used as a don't care token.

proc defaultvalues? {} {
    puts [list hey [info level -1]]
    expr {[llength [info args [lindex [info level -1] 0]]]
                - ([llength [info level -1]]-1)}
}

Trying it out:

proc test {a {b 1} {c 2}} {puts [defaultvalues?]}

test 1 ;# -> 2

test 1 2;# -> 1

test 1 2 3;# -> 0

HaO 2011-03-16: The above is very good. I have seen an in-procedure variant like that:

proc c {a {b {}}} {
    if {2 == [llength [info level 0]]} {
        # code when optional argument b was not passed
    }
}

Doing nonsensical things with default args:

proc defaultargs {{def a} undef} {
    puts [format {def: "%s", undef: "%s"} $def $undef]
}

defaultargs x y ;# -> def: "x", undef: "y"

catch {defaultargs x} msg
puts $msg ;#-> wrong # args: should be "defaultargs ?def? undef"

Determine the Name of the Current Procedure

proc foo args {
    puts "proc = [lindex [info level 0] 0]"
}

See: info level

Clobbering Existing Procedures

When creating a new procedure, no warning is given when it replaces an existing procedure by the same name. If, for example, in a Tk applicaiton, you were to code:

proc . {} {}

you would find that you have destroyed the default toplevel, likely causing the app to exit.

This is because for each widget, there exists a procedure whose name is the path of the widget. Defining a proc having the name of any existing widget has the potential in the very best cases to result in strange behavior and, and in worse cases to be catastrophic.

Procedure Compilation

bytecode
The main page on the subject.
Proc to bytecodes: when, how does it happen

The Empty Name

AMG PYK: The name of a procedure can even be the empty string, {}, but this has a weird interaction with rename and the way we abuse it for deleting procs.

proc {} {} {
    puts "my name is [info level 0]"
}

{} ;# -> my name is {}
rename {} {}
catch {{}} msg einfo
puts hello
puts $msg ;# -> invalid command name ""

Also strange: you can create a proc named by the empty string, {}, but you can't use rename to move it.

DKF: You can, but only by renaming to a fully-qualified name, like ::.

Illegal Names

Except to separate namespace parts the namespace separator string, ::, cannot occur in the name of a procedure. This is considered an unfortunate development by some Tcl programmers: If the notation for a procedure name was a list, it would have remained possible to give a procedure any name at all.

See also CMcC's comments in the Tcl Chatroom, 2014-11-28.

Pass by Reference

Implicit upvar
RS's take on the matter
deref
Larry Smith's approach

DKF 2003-07-16 PYK 2013-09-01: Here's a way to do magic "dereferencing" which C hackers might like...

proc proc2 {name arglist body} {
    set header {}
    foreach a $arglist {
        if {[string first * $a] == 0} {
            append header "upvar 1 \[set $a] [string range $a 1 end];"
        }
    }
    proc $name $arglist $header$body
}

proc2 test {a *b c} {puts "a=$a, b=$b c=$c"}
set quantity 4
test 1 quantity 3 ;# -> a=1, b=4, c=3

Naming Hacks

Any string is potentially valid proc name.

RS: "Any string" includes things that look like array elements (but aren't), with which you can emulate "arrays of function pointers":

% proc f(1) {} {puts hello}
% proc f(2) {} {puts world}
% proc f(3) {} {puts again}
% for {set i 1} {$i<=3} {incr i} {f($i)}
hello
world
again

And a certain introspection is possible too:

info proc f(*) => f(1) f(2) f(3)

Update 2002-11-15: You don't have to stop at simulating - you can just have arrays of function pointers!


A procedure name that starts with a hash character # can be called by somehow ensuring the # isn't the first character:

proc #test {} {puts [lindex [info level 0] 0]}
\#test ;# -> #test
{#test} ;# -> #test
\x23test;# -> #test
[namespace current]::#test ;# -> ::::#test

Remember that comments are detected prior to the evaluation of a script. # has no importance when commands are being evaluated.

Misc

Every set of cards made for any formula will at any future time recalculate that formula with whatever constants may be required. Thus the Analytical Engine will possess a library of its own.

- Charles Babbage, 1864