TIP 508: New subcommand [array default]

Login
Author:         Frédéric Bonnet <fredericbonnet@free.fr>
State:          Final
Type:           Project
Vote:           Done
Created:        13-May-2018
Post-History:   
Keywords:       Tcl,array
Tcl-Version:	8.7
Vote-Results:   9/0/0 accepted
Votes-For:      DKF, BG, KBK, JN, JD, DGP, FV, SL, AK
Votes-Against:  none
Votes-Present:  none
Tcl-Branch:     tip-508

Abstract

This TIP proposes a new array default subcommand that allows default values for arrays, causing that value to be returned any time an attempt is made to access an element of the array that isn't present.

Rationale

For its most basic usages, a Tcl array variable can be seen as a collection of scalar variables. However, an array also has its own existence as a variable and provides specific actions on itself and on its elements. For example, the regular way to check for a variable existence is to use the info exists command, and this is true for both arrays and scalars (including array elements); likewise, the set command is used to read and write the value of a scalar vars and array elements, however array variables have no value in themselves but provide a way to get and set several of its elements at once.

For more advanced usages, one must get past the simple ‘collection of scalars’ viewpoint. A typical example is sparse arrays: at present, an implementation requires a combination of array exists (or info exists) and set: the former to test the presence of a key in an array, the latter to get or set the actual value, and in this order. Failure to do so is likely to cause runtime errors.

This document therefore proposes to add a new array default subcommand in order to support such advanced usages. Sparse arrays built on this new feature will be simpler, more robust, faster, all the while being memory efficient.

Specification

A new array default subcommand will be created with the following syntax:

array default option arrayName ?arg?

The following option values are supported:

The default value is only used as a last resort where an error would normally occur when accessing a non-existing array element. Read traces are processed before that point and are free to set the element value or modify the default value.

Examples

Initial state:

% array exists var
0
% array default exists var
0
% array default get var
"var" isn't an array
% set var(a)
can't read "var(a)": no such variable

Create array with one entry:

% set var(a) 1
1
% array exists var
1
% array default exists var
0

Attempt to read missing entry:

% set var(b)
can't read "var(b)": no such element in array

Add default value:

% array default set var 0
% array default exists var
1
% array default get var
0

Default value is only used by missing entries:

% info exists var(a)
1
% set var(a)
1
% info exists var(b)
0
% set var(b)
0

You can lappend to a missing entry and it will behave as expected:

% lappend var(b) 2
0 2
% info exists var(b)
1

Changing the default value only impact missing entries:

% info exists var(c)
0
% set var(c)
0
% array default set var 9999
% array default get var
9999
% set var(a)
1
% set var(b)
0 2
% set var(c)
9999

Default values do not show in entry lists or iterations:

% array names var
a b
% array get var
a 1 b {0 2}
% array for {key value} var {puts "$key => $value"}
a => 1
b => 0 2
% parray var
var(a) = 1
var(b) = 0 2	

Unsetting the default value will only invalidate missing entries:

% array default unset var
% set var(a)
1
% set var(b)
0 2
% info exists var(c)
0
% set var(c)
can't read "var(c)": no such element in array

Setting the default value on a non-existing variable implicitly creates an array variable:

% array exists var2
0
% array default set var2 1234
% array exists var2
1

The default value can be set, modified or unset by trace handlers:

% set var(foo)
can't read "var(foo)": no such element in array
% trace add variable var read setRandomDefault
% proc setRandomDefault {name1 name2 op} {
    upvar $name1 arr
    array default set arr [expr {rand()}]
}
% set var(foo)
0.9132881643778124
% set var(foo)
0.6341786978925479
% set var(foo)
0.5976932461362766 
% array default get var
0.5976932461362766
% proc setRandomDefault {name1 name2 op} {
    upvar $name1 arr
    array default unset arr
}
% set var(foo)
can't read "var(foo)": no such element in array

Implementation

The proposed implementation is available on branch tip-508 in the Tcl Fossil repository.

Copyright

This document has been placed in the public domain.