TIP 479: Add Named Procedures as a New Command in Tcl (dictargs::proc)

EuroTcl/OpenACS 11 - 12 JULY 2024, VIENNA
Author:         Sean Woods <[email protected]>
State:          Draft
Type:           Project
Vote:           Pending
Created:        23-Oct-2017
Keywords:       Tcl,procedure,argument handling
Tcl-Version:    9.1
Tcl-Branch:     tip479


This TIP proposes an enhancement of the Tcl language to support named parameters when defining a procedure or OO method.


Adding a new argument to commands and methods is fairly simple in Tcl. Finding all of the references in your code where you call that command (or method) and padding in extra arguments, however, is a painful process. A pain that is magnified exponentially with the size of your project. While Tcl does provide the args facility for optional arguments beyond the positional arguments, that facility does not populate local variables, nor provide any meaning or context beyond Thar be dragons. Well, dragons and a list called args that the called procedure needs to sort out by itself.

In the Tk world we mitigate this complexity by having as few positional arguments as possible, and instead providing a rich set of optional configuration flags. The flipside is that those options need to be registered ahead of time in a massive option database. Also, those option/value pairs go directly into C data structures, they at no time feed a script with local variables.


Currently if the last parameter named args is given a default value, that information is essentially ignored by the workings inside of tclProc.c. This spec calls for storing information where a default would go for the args parameter. This information is a list with two fields: namespace and spec.

namespace is a Tcl namespace which contains the command parse. Parse accepts two arguments, a specification and list containing the contents of args. How that specification is interpreted is left to the imagination of the developer.

This spec defines a reference implementation called dictargs.


Under dictargs the specification for non positional arguments is a dict. Each key in the dict will be the name of a local variable that the parameter will populate. The values in the dict will be a key/value set of configuration option for the named parameter system.

Developers can specify if a named parameter has a default if not given. They can also specify if the parameter is required. A non-mandatory parameter will not be mapped to a local variable, but it is understood that developers may want to advertise their presence for documentation purposes.

	proc foo {{{args {dictargs {
	   bar {default: baz}
	}}}}} {
	   puts $bar
	% foo
	> baz
	% foo bar bing
	> bing

The primary use case for this tip are new functions that take only named parameters. This TIP also calls for the creation of a new command, provisionally named dictargs::proc. dictargs::proc will accept the same number of arguments as proc, but the format for the argument spec will be a dict instead of a list. For TclOO methods, we will also create an dictargs::method as a shortcut to:

	oo::define myclass method namemethod {{{args {
	   bar {default: baz}
	}}}} { ... }

The workings of dictargs::proc and dictargs::method are essentially:

	proc dictargs::proc {name argspec body} { proc $name [list [list args $argspec]] $body }

Named parameters are designed to be deliverable in any order. Named parameters can also be omitted. As such it is necessary to provide more introspection than the current incarnation of proc provides. If the procedure requires more than the default behavior or populating local variables with the argumemnt given, it can introspect with $args with [dict exists] to detect if an argument was given at all. The argument spec that was given by the developer will also be available as the local variable argspec.

	% dictargs::proc p {a {default: {}}} { return $args }
	% p
	a {}
	% p foo bar
	a {} foo bar

Given these rules, we can get into non-intuitive failures. For instance, what would happen if a default value was not provided for an argument, and the command call does not provide one.

	% dictargs::proc q {a {default: {}} b {}} {
	  # Proc assumes b will be given
	  set result [list $a $b]
	  foreach {f v} $args {
	    if {$item ni [dict keys ${argspec}]} {
	       lappend result $item [dict get $args $item]
	% q
	error: No such variable b ; # Stack trace points to first use if $b
	% q b foo bar baz
	a {} b foo bar baz

It would be helpful to have that field (b) treated as a mandatory argument, and have the error thrown in the procedure's argument parser, not in the procedure itself. After some play testing with the rules I have worked out what properties will be tracked for each named parameter, and how those properties are calculated if not specified directly in the options.

Configuration Options for Dictargs Named Parameters

The following general rules apply to all values given in the configuration dict for a named parameter.

  1. If a value for any option is given as an empty string, it is inferred to be null.
  2. A variable will be created with the same name as the named parameter.
  3. A field that is given, but not otherwise reserved by the spec, is ignored by the parser but made available for introspection via the argspec dictionary.
  4. If the same option is given multiple times, the last value is the one that will be used. (Similar to [dict merge]).


Type: boolean Default: true

Set to false if default given. If true, throw an error if the named parameter is not given.


Type: value Default: null

When non-null, if a named parameter is missing from the call assume the value specified.


	% dictargs::proc u {
	  a { default: {A useful value} }
	} {
	  puts $a
	% u
	> A useful value
	% u a {Less usefull}
	> Less usefull


Type: list Default: null

A list of alternative names for this parameter. To resolve any potentials conflicts, the following rules apply to aliases:

  1. If a parameter with the canonical name is given, alternatives will be ignored.
  2. If multiple non-canonical parameters are given, the last value given will be the value for the canonical field.


	% dictargs::proc u {
	  a {
	    default: {A useful value}
	    aliases: {alpha A}
	} {
	  puts [list $a $args]
	% u
	> {A useful value} {}
	% u a {Less usefull}
	> {Less usefull} {a {Less usefull}}
	% u alpha {Less usefull}
	> {Less usefull} {a {Less usefull} alpha {Less usefull}}
	% u a {Don't be pedantic} alpha {Less usefull}
	> {Don't be pedanditic} {a {Don't be pedantic} alpha {Less usefull}}


Type: string Default: null Options: null, boolean, integer, wide, entier, double

To support tip#480. For now strictly advisory. In the future this will check that the incoming values are the specified type.


This TIP will be rolled out in 3 stages.

Stage 1 - Pure Tcl (finished)

Stage 1 is a pure-tcl implementation to allow the community to try out the rules and see if this new concept is a good fit for Tcl. The implementation will use a macro-like template to place the implementation rules as a pre-amble to the body given to dictargs::proc or dictargs::method. This implementation will affect line numbers in traces, but should provide the new features without introducing a major impact on performance.

This implementation has been posted to the http://wiki.tcl.tk/49071 and will be kept up to date.

State 2 - C Extension (finished)

Stage 2 will be a standalone TEA extension compatible with Tcl 8.6. In this implementation a C API call will create a command argsx which will take as input the parameter specs for the proc and the value of the args variable. The argsx command will then provide the local variables for this tip's rules prior to passing control to the user's body.

To build the extension:

  mkdir tip479
  cd tip479
  fossil clone http://fossil.etoyoc.com/fossil/tip479 tip479.fossil
  fossil open tip479.fossil
  tclsh make.tcl all

State 3 - Core patch

The original specification required modifications to the core. These modifications, while they did not seem to impact normal operations, also did not seem to actually improve performance over the pure-tcl or C compiled extension.

The modifications


In TclCreateProc, if an named argument spec was registered for the function, additional localPtr entries are registered beyond args to hold the expected values added by the spec.


In ProcWrongNumArgs, the contents of the argument spec are reported instead of ?args ? if an argument spec was registered for that proc.


In InitResolvedLocals, we modify the loop to also resolve variables in the compiledLocals beyond the numArgs.


During standard argument processing, the function notes if the procedure has a named argument spec registered. If a spec was present, after it performs its normal population of local variable matching the parameters for the procedure, the function calls ProcEatArgs which pairs key/value pairs beyond the last positional parameter with registered name parameters (and their associated local variable.


Two macros for dictargs::proc and dictargs::method are placed in the init.tcl file to ensure those facilities are present at runtime. The community is presently in a debate as to whether seperate commands are the best approach, or modifying proc and oo::define::method to accept flags is better.

info argspec

In the info namespace, add an argspec command to return the named argument spec for a command


Included in the file distribution is a test script which produces performance metrics. The most up to date metrics are posted to:


Long story short, for the C implementation, processing arguments using the argsx mechanic is faster than performing [dict with args {}]. An equivilent proc with positional arguments is still slightly faster.

The core implementation is faster than the C implementation, without introducing any slowdown for conventional procs.

The code to produces the benchmarks is included in the code repository:



While this tip addresses the design patterns of proc definitions.

Arguments for ensemble-like procs

The argsx command is available for use at any place within Tcl, not just at the top of a command. Consider:

proc myensemble {command args} {
  switch $command {
    smtp {
       # Dicts can be created on the fly
       argsx [dict create from {} to {} mtime [dict create default: [clock seconds]]] $args
       smtp::dispatch $from $do mtime $mtime
     http {
       # Or accept a static string with whitespace
       argsx {
          from {}
          to {}
          url {default: http://mydomain.info}
       } $args
       http::post ${url}?from=${from}&to=${to}

Extra Metadata for argument handling

The standard only defines the behavior for several properties for parameters, but does not prohibit storing additional properties. Consider:

dictargs::proc prettyproc {
  color {
    comment: {Color of the button}
    type: color
    default: green
  flavor {
    comment: {What flavor candy emerges when pressed.}
    default: random
    options: {vanilla caramel chocolate strawberry}
} {
   set flavors  [dict get [info argspec prettyproc] flavor options:]
   if {$flavor eq "random"} {
     set flavor [lrandom $flavors]
   } else {
     if {$flavor ni $flavors} {
        error "Invalid flavor $flavor. Valid: random OR $flavors"

Spec Revisions

2018-10-19 - Replaced the idea that this tip would be providing the only means of named argument parsing with the concept that this tip would provide the shims for developers to provide such a mechanism, as well as provide a reference implementation called "dictargs".

Renamed procx to dictargs::proc and methodx to dictargs::method, and clarified that argument parsing will be performed by dictargs::parse

Removed the core patches, and instead casting this TIP as an information one for developers who wish to either exploit dictargs or utilize their own non-positional argument processing scheme.

2017-11-03 - Renamed @proc to procx, @args to argsx and @method to methodx. Removed the internal variable @spec from bodies. Added

2017-10-28 - Removed the variable option. The core implementation really needs a canonical name for the variable in the index. If a user really wants a different name in the arguments, we have the aliases facility. Also clarified that this tip will be modifying how a default for the args parameter is interpreter and that procx and methodx are just convenience wrappers.