This is a consolidation and update of various discussions for ways to streamline and improve the syntax necessary to do calculations in Tcl.
The expr command is widely viewed as one of Tcl's more ungainly warts. There have been many unimplemented or rejected TIPs for changes related to improving or doing away with the expr command over the years:
https://core.tcl-lang.org/tips/doc/trunk/tip/133.md
https://core.tcl-lang.org/tips/doc/trunk/tip/266.md
https://core.tcl-lang.org/tips/doc/trunk/tip/282.md
https://core.tcl-lang.org/tips/doc/trunk/tip/408.md
https://core.tcl-lang.org/tips/doc/trunk/tip/526.md
TIPs related to the current discussion include:
TIP 672: Extend $ substitution to include expressions as $(expression)
TIP 674: a new multiple expression command
TIP 676: An ”expr” alternative - ”calc” command aliased to ”=”
Another Wiki discussion page is also present: Single quotes to denote expr.
See also the extensive discussion at expr shorthand for Tcl9.
Several syntactic proposals have been considered with various possible side effects both to potentially existing code. These have been discussed extensively on the TCLCORE mailing list, the Tcl Chat, and comp.lang.tcl. Some prototypes have been completed and alternatives debated.
Currently, there are several proposals being considered:
| Syntax | Benefits | Drawbacks | Prototype |
|---|---|---|---|
| $( $a + $b ) - invokes expr under the covers (TIP 672) | Simple syntax | $*, Breaks unnamed array access which also looks like $( ... ) | tags: tip-672, core-tip-672 in Tcl Fossil repo |
| $(( $a + $b )) - invokes expr under the covers | Simple syntax, similar to Bash shell, no known incompatibilities | $* | |
| [( $a + $b )] - invokes expr under the covers | Simple syntax, more similar to existing command substitution syntax instead of variable syntax | Collides with proc ( {} | |
| [= $a + $b ] - invokes expr under the covers (TIP 676) | Simple syntax | Collides with proc = {} | |
| let ... { $a + $b } - new command that allows multiple calculations (TIP 674) | |||
| [= a + b ] - new syntax that directly uses variables by name without the '$' character. | Simple syntax, Also can use assembled byte code for performance | Collides with proc = {} | https://cmacleod.me.uk/tcl/expr_ng2 - Colin Macleod's calc (=) with caching, extensions, and test suite |
| set varName = {$a + $b} - extension of "set" command to include simple math. | Does not break any rules of Tcl | set varName = expr | |
| {=}{$a + $b, $c + $d} - Variant of the {*} expansion prefix (expands the arguments), but instead of a list, it takes a comma-separated list of expressions, exactly how arguments are parsed in a Tcl math function. | Cleans up command calls with multiple numeric inputs | ||
| expr {($x,$y)} - Native list notation within Tcl expressions | Easy definition of lists with numeric inputs |
$* - these proposals involve changing the '$' from meaning purely variable expansion, to meaning variable expansion and some other things.
There are essentially two main alternative in terms of overall syntax:
FM Notice that the '[( ... )]' variant is detected by the Tcl Parser itself (in 'Tcl_ParseToken'), so it's not a new command at all. Contrary to the '[= ...]' proposal, '[( ... )]' is a new syntax rule, dedicated to math expression substitution. It will have to appear as the thirteen rule of a new Tcl tridekalogue, whose title could be « Mathematical Expression substitution ». Notice also, that a '(' proc could still be allowed, the only exception beeing when the last char of the nested expression is a closed parenthesis.
I created a repository on github to work on it. Some code are there, imported from my computer.At home, it's working, Some bugs to investigate thought...
I add a variation, specifically for array index, It writes then :
set A((1+1)) hello # set A(2) hello.
Using the same logic, I'd like also to add the possibility of creating mathfunc like this :
proc func {x} {(
...
)}ie : if the body of a proc (or of a lambda, or of a method) begins by a '(' and finish by a ')', then, this proc is to be taken as a mathematical function and then must be compile as a mathematical expression. It may be usefull when writing multilines expression will be made possible.
There are also two main alternatives for the expression language:
CGM These two issues (whether to change general Tcl syntax or introduce new expression syntax) are related. A use of the existing expr needs two levels of bracketing - [expr {expression}] - because it has to both invoke the expr command and protect the expression from double substitution.
If you introduce a new form like $((expression)) which requires a change to the dodelalogue, that form can be specified to do both these jobs, and then you can use the existing expr syntax unchanged for the expression, e,g. $(($x+2*$y)).
If you stick to the existing dodekalogue rules as [= expression] does, you can only prevent double substitution by changing the syntax of the expression itself. I originally proposed in TIP 676 that = would take its arguments pre-substituted and do no substitution itself. This requires the same example to be written as [= $x + 2 * $y].
I now prefer the option of having = do substitution on barewords, which would not be pre-substituted anyway. This enables the more compact form [= x+2*y].
Colin's syntax (bare variables, no $) by EricT (the command name ":" is open for suggestions, Colin's implementation uses the command name =):
: {x = 1+2
y = 3*4
z = x+y}NR - 2025-11-10 13:03:27
I don't know if this will change the philosophy or the core of the Tcl language much, but I liked this version of AMB with the equal sign https://wiki.tcl-lang.org/page/set+varName+%3D+expr
jmc123 - 2025-11-13 17:10:46
Be aware that ":" command is used by the SQLite package interface to Tcl for dereferencing variables (very usefull when the developper wants this derefencing *not* done at Tcl level but at SQL level)
see end of chapter "3.1. The eval method" in the doc at https://sqlite.org/tclsqlite.html
Jean-Marie
AMB - 2026-02-11 23:13:55
I have added my proposal for {=}{$a + $b, $c + $d} to the table of suggestions here. I think it is the most viable option, especially for the case where multiple math expressions are needed for input to a single command, such as the notorious example below:
.canvas create rectangle [expr {$w-100}] [expr {$h-40}] [expr {$w+100}] [expr {$h+40}]; # This is obtuse :(Here is the same command, in all the shorthand options shown in the table above:
.canvas create rectangle $($w-100) $($h-40) $($w+100) $($h+40); # $($a + $b)
.canvas create rectangle $(($w-100)) $(($h-40)) $(($w+100)) $(($h+40)); # $(($a + $b))
.canvas create rectangle [($w-100)] [($h-40)] [($w+100)] [($h+40)]; # [($a + $b)]
.canvas create rectangle [= $w-100] [= $h-40] [= $w+100] [= $h+40]; # [= $a + $b]
.canvas create rectangle [= w-100] [= h-40] [= w+100] [= h+40]; # [= a + b]
.canvas create rectangle {=}{$w-100,$h-40,$w+100,$h+40}; # {=}{$a + $b, $c + $d}The {=} prefix notation (the math version of the expansion prefix) uses the least number of characters, and in my opinion feels much more natural to write, as it is identical to the notation used for entering multiple arguments in a expr math function.
I also recommend that the notation "set varName = {$a + $b}" be added as well, as it handles one of the most common uses of expr. These two additions should alleviate most of the pain points of doing math in Tcl.
With these two changes, let's take a look at some functions in Tcllib and how they would be improved:
1. ::math::linearalgebra::crossproduct
Before:
proc ::math::linearalgebra::crossproduct { vect1 vect2 } {
if { [llength $vect1] == 3 && [llength $vect2] == 3 } {
foreach {v11 v12 v13} $vect1 {v21 v22 v23} $vect2 {break}
return [list \
[expr {$v12*$v23 - $v13*$v22}] \
[expr {$v13*$v21 - $v11*$v23}] \
[expr {$v11*$v22 - $v12*$v21}] ]
} else {
return -code error "Cross-product only defined for 3D vectors"
}
}After (and with a little refactoring):
proc ::math::linearalgebra::crossproduct { vect1 vect2 } {
if { [llength $vect1] != 3 || [llength $vect2] != 3 } {
return -code error "Cross-product only defined for 3D vectors"
}
lassign $vect1 v11 v12 v13
lassign $vect2 v21 v22 v23
list {=}{$v12*$v23 - $v13*$v22, $v13*$v21 - $v11*$v23, $v11*$v22 - $v12*$v21}
}2. ::math::calculus::integral
Before:
proc ::math::calculus::integral { begin end nosteps func } {
set delta [expr {($end-$begin)/double($nosteps)}]
set hdelta [expr {$delta/2.0}]
set result 0.0
set xval $begin
set func_end [uplevel 1 $func $xval]
for { set i 1 } { $i <= $nosteps } { incr i } {
set func_begin $func_end
set func_middle [uplevel 1 $func [expr {$xval+$hdelta}]]
set func_end [uplevel 1 $func [expr {$xval+$delta}]]
set result [expr {$result+$func_begin+4.0*$func_middle+$func_end}]
set xval [expr {$begin+double($i)*$delta}]
}
return [expr {$result*$delta/6.0}]
}After:
proc ::math::calculus::integral { begin end nosteps func } {
set delta = {($end-$begin)/double($nosteps)}
set hdelta = {$delta/2.0}
set result 0.0
set xval $begin
set func_end [uplevel 1 $func $xval]
for { set i 1 } { $i <= $nosteps } { incr i } {
set func_begin $func_end
set func_middle [uplevel 1 $func {=}{$xval+$hdelta}]
set func_end [uplevel 1 $func {=}{$xval+$delta}]
set result = {$result+$func_begin+4.0*$func_middle+$func_end}
set xval = {$begin+double($i)*$delta}
}
return {=}{$result*$delta/6.0}
}Edit: I have to say, I really like how Tcl looks with these two changes. I think it really fits with the feel of Tcl. Like, please let's add this to the core. It would eliminate 99% of expr calls without fundamentally changing the philosophy of Tcl. It wouldn't even change the Dodekalogue, it would simply modify the argument expansion rule to include math expansion as well ({*} for expanding Tcl lists, {=} for expanding comma-separated Tcl expressions). I think this is a winner.
AMB - 2026-02-21 00:16:44
Upon much reflection and discussion on the wiki, I am now in favor of FM’s [(math)] syntax as shorthand for [expr {math}]. I think it should be added to the core.
Additionally, I think that ($x,$y) should be added as native list syntax within a Tcl expression. Then, the proposed options can be rewritten as follows:
.canvas create rectangle [expr {$w-100}] [expr {$h-40}] [expr {$w+100}] [expr {$h+40}]; # where we are
.canvas create rectangle {*}[expr {($w-100,$h-40,$w+100,$h+40)}]; # regular expr with ($x,$y) list syntax
.canvas create rectangle {*}$(($w-100,$h-40,$w+100,$h+40)); # $($a + $b) with ($x,$y) list syntax
.canvas create rectangle {*}$((($w-100,$h-40,$w+100,$h+40))); # $(($a + $b)) with ($x,$y) list syntax
.canvas create rectangle {*}[(($w-100,$h-40,$w+100,$h+40))]; # [($a + $b)] with ($x,$y) list syntax
.canvas create rectangle {*}[= ($w-100,$h-40,$w+100,$h+40)]; # [= $a + $b] with ($x,$y) list syntax
.canvas create rectangle {*}[= (w-100,h-40,w+100,h+40)]; # [= a + b] with ($x,$y) list syntax
.canvas create rectangle {=}{$w-100,$h-40,$w+100,$h+40}; # {=}{$a + $b, $c + $d} (shorthand for {*}[expr {(...)}])nektomk - 2026-02-23 19:34:10
I believe that it is better to improve the syntax of expr than to make changes to the language itself. For example, so that expr can return a list (vector) instead of just a single scalar value:
set w 200
set h 200
.canvas create rectangle {*}[expr {$w-100 $h-40 $w+100 $h+40} ]; # 100 160 300 240nektomk - 2026-02-23 20:33:49
you can implement the vector function as expr with several arguments:
namespace eval ::tcl::mathfunc {
proc vector { args } {
set res {}
foreach item $args {
if { [ llength $item ] == 1 } {
lappend res [ uplevel [ list expr $item ] ]
} else {
lappend res $item
}
}
set res
}
namespace export vector
}
set x 10
puts [ expr { vector(1+2, $x*2) } ] ;# 3 20 ; using inside expr []
puts [ ::tcl::mathfunc::vector 100-10 $x^2 ] ;# 90 100 ; using w/o import
namespace import ::tcl::mathfunc::vector
puts [ vector [ vector 1 2 ] [ vector 3 4*$x ] ] ;# {1 2} {3 40} ; using imported
# test with rectangle
package require Tk
canvas .canvas
set w 100
set h 100
.canvas create rectangle {*}[vector {$w-100} {$h-40} {$w+100} {$h+40}] ;# 0 -60 200 140 ; it`s more tcl-way
#.canvas create rectangle {*}[vector $w-100 $h-40 $w+100 $h+40] ;# 0 -60 200 140 ; also work
pack .canvas
AMB - 2026-02-23 22:37:39
RE: nektomk
1. If expr returned multiple arguments, it would break existing code where expr returns a list.
For example:
# Current behavior
set sayhello true
puts [expr {$sayhello ? "hello world" : ""}]; # hello world
# If expr returned a list
puts [expr {$sayhello ? "hello world" : ""}]; # {hello world}This is why I propose that ($x,$y) be native syntax for a list in a Tcl expression, because then it removes the ambiguity. Plus, if I am not mistaken, all instances where you would use parentheses in a Tcl expression is when you are dealing with numeric input, which does not include whitespace in the string representation. Therefore, it should be fully backwards-compatible.
set w 200
set h 200
.canvas create rectangle {*}[expr {($w-100,$h-40,$w+100,$h+40)}]; # 100 160 300 2402. A mathfunc within a Tcl expression already passes its inputs through "expr". So, yes you could define a mathfunc (such as "vector") to achieve this, as shown in the functional code below:
proc ::tcl::mathfunc::vector {args} {
return $args
}
set x 2.0
set y -1.0
set z 5.0
puts [expr {vector($x+1,$y,$z*2)}]; # 3.0 -1.0 10.0
# Does not work outside of "expr"
puts [vector $x+1 $y $z*2]; # 2.0+1 -1.0 5.0*2Admittedly, this does not work outside of the expr environment. Your implementation does seem to solve this problem, but at the expense of creating a surprise double-substition problem. Besides the issue with performance, this allows for sneaky code injection.
# THIS FUNCTION ALLOWS FOR CODE INJECTION, DO NOT USE.
namespace eval ::tcl::mathfunc {
proc vector { args } {
set res {}
foreach item $args {
if { [ llength $item ] == 1 } {
lappend res [ uplevel [ list expr $item ] ]
} else {
lappend res $item
}
}
set res
}
namespace export vector
}
# "dangerous virus"
proc virus {} {
exec notepad.exe &
return 10
}
set x {[virus]}; # code injection
# code will run normally, passing all tests, but will spawn a background process as well.
puts [ expr { vector(1+2,$x) } ]; # 3 10
puts [ vector 1+2 $x ]; # 3 10Tcl's popularity suffers largely, IMO, from how annoying it is to do simple math. While improvements to the expr syntax are good, I think it is a big enough pain point to justify adding a rule to the language.
<<FM>> NEWS : I had a try with the native list handling. I just created a list operator node on top of the start node in ParseExpr routine, and also add Tcl_ListObjCmd as math func in tclBasic.c . As the shorthand allways begin and finish with parenthesis, it works immediately (the open paren is resolved in function). Now, the shorthand will recognize something like :
set w 200
set h 200
set rectangle [($w-100, $h-40, $w+100, $h+40)]; # ok -> 100 160 300 240
# notice :
expr {(1,1)}; # ok -> 1 1But you can't write everything :
set v [(100,100,100)] ; # ok -> 100 100 100
set u [(200,200,200)] ; # ok -> 200 200 200
set s [($u+$v)] ; # error : cannot use a list as left operand of "+"
# neither :
set r [((100, 100),(100,100))]; # error : unexpected "," outside function argument list in expression "((100, 100),(100,100))]"
# you'll have to write, using the nesting expr shorthand capabilities :
set r [( [($w-100, $h-40)] , [($w+100, $h+40)] )]; # ok : {100 160} {300 240}
set Id [(
[(1,0,0)],
[(0,1,0)],
[(0,0,1)]
)] ; # ok -> {1 0 0} {0 1 0} {0 0 1}I still have to export the changes on the repository. <</FM>>
AMB - 2026-02-24 15:25:56
That’s great FM!
I was thinking that this would be shorthand for a list with your syntax:
set x 5
set y 3
set vector [(($x+1,$y*2,$x+$y))]
# set vector [expr {($x+1,$y*2,$x+$y)}]As in, you need an extra pair of parentheses to denote a list. The first pair in [(...)] is just the expr shorthand syntax.
<<FM>> 2026-02-25 : As the shorthand syntax is already enclosed with parenthesis, I didn't need to add an extra level of it. Also, as it is done now, you can write expr {(1,1)} to get the same list effect. <</FM>>
<<AMB>> - 2026-02-24 : What I am envisioning is that an open parentheses within an expression denotes the beginning of a list. This way, the code you presented would be written like this:
set v [((100,100,100))] ; # ok -> 100 100 100
set u [((200,200,200))] ; # ok -> 200 200 200
set s [($u+$v)] ; # error : cannot use a list as left operand of "+"
# ^ We would need list operators for this (e.g. `.+`).
set r [(((100, 100),(100,100)))]; # {100 100} {100 100}
set w 200
set h 200
set r [((($w-100, $h-40),($w+100, $h+40)))]; # ok : {100 160} {300 240}
set Id [((
(1,0,0),
(0,1,0),
(0,0,1)
))] ; # ok -> {1 0 0} {0 1 0} {0 0 1}<<AMB>>
<<FM>> 2026-02-25 : I wanted to do something like this to. The problem was that, when the expr parser see the comma outside func arguments error, it is already trying to complete an OPEN_PAREN unary operator : As operators are stored in a continuous C-array, there is no space in the array to add a new node for the list FUNCTION unary operator. That's why I shifted my view, and just add, by default, a list FUNCTION operator node just after the START node. Naturally, the OPEN_PAREN will go to complete this list FUNCTION operator node I inserted, already waiting for its arguments list, ... then no comma outside func arguments problem any more. <br>>
Yes, the way I did it, the shorthand allways return a list. And it's working at the root of expression only. But, some side effects should be investigated and corrected (in tclinit, especially).
That said, I think I found a solution to generalize this list syntax to the one you just exposed : What if, if we create, in the array of node, by default, a FUNCTION node operator before every OPEN_PAREN node operator ? If there is no comma outside func arguments error, this FUNCTION operator will be a NO_OP operation (returning exactly what it get), else, it will be the set to be the list FUNCTION. I will try this approach I think.<</FM>>
<<AMB>> - 2026-02-24
Let's say that you defined a mathfunction called "cross" for doing a cross-product of two vectors. Then, you could, with the syntax I am envisioning, do this:
set x 5 set result [( cross(($x*2,1,0),($x+2,0,1)) )]; # 1 -10 -7
This is consistent with the syntax of math functions. I am proposing that parentheses get interpreted exactly how they are interpreted for math functions.<</AMB>>
<<FM>> 2026-02-25 : actually with the shorthand you can do, because I linked list as in the mathfunc table :
proc tcl::mathfunc::crossSH {U V} {
lassign $U x y z
lassign $V u v w
return [( $y*$w - $v*$z,
$w*$x - $z*$u,
$x*$v - $u*$y )]
}
proc tcl::mathfunc::crossExpr {U V} {
lassign $U x y z
lassign $V u v w
return [list \
[expr {$y*$w - $v*$z}] \
[expr {$w*$x - $z*$u}] \
[expr {$x*$v - $u*$y}]]
}
set x 5
puts [( cross(list($x*2,1,0), list($x+2,0,1)) )] ; # {1 10 -7}
puts SH:[timerate {
set res [( crossSH(list($x*2,1,0), list($x+2,0,1)) )]
}]
# -> SH:9.296158 µs/# 107571 # 107571 #/sec 999.997 net-ms
puts EXPR1:[timerate {
set res [expr {crossExpr([list [expr {$x*2}] 1 0], [list [expr {$x+2}] 0 1]) }]
}]
# -> EXPR1:14.7362 µs/# 67860 # 67860.0 #/sec 1000.000 net-ms
# Shorthand is faster !
# But, of course, I'd like to write this instead :
puts [( crossSH(($x*2,1,0), ($x+2,0,1)) )]
# And logically get : unexpected "," outside function argument list<</FM>>
<<AMB>> Edit: Additional note, I have proposed elsewhere that the syntax {commandPrefix}(arg,arg) be implemented within expressions to allow for any Tcl command to be used in a Tcl math expression. Then, you could do stuff like this:
set x 2
set n 5
set result [( {concat}(($x*2,$x-1),{lseq}(1,$n+1)) )]; # 4 1 1 2 3 4 5 6
# set result [concat [list [expr {$x*2}] [expr {$x-1}]] [lseq 1 [expr {$n+1}]]]; # Equivalent This would essentially make {commandPrefix} something that is applied to a Tcl expression list. By default, (expr,expr) would just create a list. But if it is prefixed with {commandPrefix}, the result of that list is appended to the command prefix and evaluated, in the same way that Tcl mathfuncs work.<</AMB>>
<<FM>> 2026-02-25 : Sorry, I can't follow this proposal. It's not a good idea to create one prefix per command we want to import in tcl::mathfunc. I take a braced-prefix as a meta-data added to a value, so it can be used by the caller to be directed on how to use this value (like a protocol). So, a prefix must have a general meaning. I proposed, some times ago, that braced-prefixes should be applyable to operands and operators. I proposed then the prefix {::}, which would indicate the expr_parser it should resolve the func name in the global namespace.
Moreover, I imagine also that those prefixes can take parameters, enclosed by parentheses, which, if many, will be separated by comma. In the {::} prefix case, there would as many parameters as nested namespaces, to indicate the full path of the command. Beyond this proposal, your example will be written like this :
# 1. Function braced-prefix idea :
# ------------------------------
set x 2
set n 5
set result [( {::}concat(($x*2,$x-1), {::}lseq(1,$n+1)) )]; # 4 1 1 2 3 4 5 6
# in any other namespace ? with path as parameters.
set [( {::(math,fourier)}dft((1,2,3,4)) )]
# Actually :
expr {{::(math,fourier)}dft((1,2,3,4))}
# -> missing operator at _@_ in expression "{::(math,fourier)}_@_dft((1,2,3,4))"About operators : Adding new operators will be quickly limited because of the lack of specific ASCII symbols. How many product can we do ?
dot product, cross product, matrix product, tensor product, complex product, quaternion product, ... etc
Too much kinds of products to have a specific operator for each... Moreover, nobody will need all of those products. Some people will need one or two of it, depending on the goal of their software. That's why I think is better to say : Ok, operators can be prefixed and configured.
# 2. Operator braced-prefix idea :
# --------------------------------
protocol expr operator * cross {v V} {...}
protocol expr operator * dot {v V} {...}
protocol expr operator * matrix {m M} {...}
protocol expr operator * tensor {t T} {...}
protocol expr operator * complex {z Z} {...}
protocol expr operator * quaternion {h H} {...}
set V [( $V1 {cross}* $V2 )]
set S [( $V1 {dot}* $V2 )]
set M1 [( $V1 {tensor}* $V2 )]
set M [( $M1 {matrix}* $M2 )]
set Z [( (1,2i) {complex}* (3,-i) )]
set H [( (1,2i,3j,4k) {quaternion}* (1,-2i,-3j,-4k) )]
# Actually :
expr {list($x*2,1,0) {cross}* list($x+2,0,1)}
# -> missing operator at _@_ in expression "list($x*2,1,0) _@_{cross}* list($x+2,0,1)"This way, we would'nt be limited in terms of symbols. The package libraries would configure it when we need it.<</FM>>
AMB - 2026-02-25 13:32:57
FM, what I am proposing is different than your concept of protocol. It wouldn’t hard-code any prefix to the interpreter. It would be separate from that. Within a Tcl expression, braced prefixes would allow for access to any Tcl command (or any command prefix). This would not carry over to command evaluation, it would only be applicable to expressions.
For operators on things like matrixes or complex numbers, I think those are best handled with functions, not new operators.<<FM>> you are thinking wrongly : infix notation, with precedence rules, can save a lot of parentheses <</FM>>.
<<AMB>> Yes, infix notation is nice, and I do think that the operators in Tcl should be able to handle more than just scalar numbers. I think that element-wise operations should be allowed. But for stuff like matrix multiplication, cross-products, and - going to the level of arcane here - multiplying quaternions, I really think it is overkill to have infix notation. Even the most popular mathematics programming languages, such as matlab or numpy, use functions for things like cross-products and multiplying quaternions. <<[AMB]>>
Also, braces followed by an operator (except parentheses) is currently allowed in an expression. Example below:
set message “hello world”
puts [expr { {hello world}eq $message }]; # 1
puts [expr {{2.0}+{5.3}}]; # 7.3<</AMB>>
<<FM>> Sometimes yes, sometimes no. In your example, {braced_word} is resolved by Tcl_parseBrace, then is given as left operand to the "+" operator, which gets the numerical value 2.0.
Let's check what is not allowed :
<</FM>>
<<AMB>>: Your proposed {::} prefix for accessing other commands in a Tcl expression is confusing IMO because it would seem to imply global access, as that is what it means in the context of procs and variables. But what if you wanted to access a local proc or command? <</AMB>>
<<FM>> I'd rather to have a finite set of string prefix, eventually with parameters, for a set of purposes (like locating a command in namespace or frame), than an infinity of different prefixes for one sole purpose. We can reserve the name{::} for namespace resolution, but use {!} for frame resolution. We would then write : expr {1+ {!(0)}mycommand(3)} to denote mycommand is to be looked in the current frame.
Moreover, those same prefixes could be used either for commands and for variables. Example :
expr {
{!(0)}CommandInMyFrame({!(-1)}$myUplevelVariable)
* {::(my,namespace)}Command({::(my,namespace)}$Var)
}<</FM>>
<<AMB>> Frankly, I don't like it. It is very messy. Using my proposed notation, it would look like this:
upvar 1 myUplevelVariable myvar
expr {
{CommandInMyFrame}($myvar) * {::my::namespace::Command}($::my::namespace::Var)
}<<FM>> Well, You are true, in saying that what I wrote is not beautifull. But, with your approach, we are missing 2 importants possibilities :
The problem, with what I wrote, may be that this form prefix braced-prefix with params between parentheses and comma-separated is not well adapted in this context : In expr, parentheses and commas are already everywhere. That's why it's not readable.
Let's suppose that, when we configure the braced-prefix, which is a allways string, we have the capability to define it through a regexp.
Then we can write it like this :
# 1. uplevel frame prefixes :
# regexp to be matched by the prefix
# |
# v
protocol -regexp expr function {^+} {prefix funcName args} {
set funclevel [string length $prefix]
return [uplevel $funclevel [list $funcName {*}$args]]
}
proc By2 {x} {
return [( $x *2 )]
}
proc By2_Plus1 {num} {
return [( {^}By2($num)+1 )]
}
set x [By2_Plus1 3]; # -> 7
protocol -regexp expr variable {^+} {prefix varname} {
set level [string length $prefix]
return [uplevel $varlevel [list set $varname]]
}
proc VarBy2_PlusVar {varname toAdd} {
return [( {^}By2({^}$varname) + {^}$toAdd )]
}
set y [VarBy2_PlusVar x x]; # -> 21
# 2. Namespaces prefixes :
# regexp to be matched by the prefix
# |
# v
protocol -regexp expr function {^(:{2}\w*)+$} {prefix funcName args} {
if {$prefix ne {}} {
set func [namespace join $namespace $funcName]
return [$func {*}$args]
}
}
protocol -regexp expr operator {^(:{2}\w*)+$} {prefix opName left right} {
if {$prefix ne {}} {
set op [namespace join $namespace $opName]
return [$op $left $right]
}
}
protocol -regexp expr variable {^(:{2}\w*)+$} {prefix varName} {
if {$prefix ne {}} {
return [namespace eval [list set $varName]]
}
}
set z {3 4}
set ::preferences::scale 2
proc Modulus z {
return [( {::preferences}$scale * ( {^}$z {::math::complexnumbers}* {::math::complexnumbers}conj({^}$z)) )]
}
Modulus z; # -> 50
I think it looks better like this. But, of course, it's allways better to use local vars and commands<</FM>>
<<AMB>> What I am proposing is that when braces are followed by an open parentheses in an expression, that the word contained within the braces is then taken to be part of a command to evaluate. It would not be hard-coded at all, it would be dynamic.
namespace eval ::math {
expr {{fourier::dft}((1,2,3,4))}
}When I say “command prefix”, I am using the same terminology as is used with the trace command. As in a list that is prepended to the remaining arguments and evaluated. <</AMB>>
<<FM>> I understood your proposal. My problem with this proposal is that braced-prefixes would then be usable for this only purpose. But braced-prefixes can have a lot more purpose than to only indicate where to locate a command. It will be the end of big amount of possibilities... That's why I'd rather use one prefix with parameters, rather than many distinct prefixes, for the same purpose (like to locate a command) : Everything Is 2 Strings in Tcl++. <</FM>>
<<AMB>> I don't think that you understand my proposal. I am not proposing anything that would interfere with your protocol concept.
I am proposing that within a Tcl expression, when a braced prefix is followed by open parentheses, it is shorthand for parsing arguments in the style of a mathfunc, appending then to the command contained within the braces, and evaluating that command.
It would not interfere with any of your other proposed use-cases. It is specifically the syntax {commandPrefix}(arg,arg...), within the context of a Tcl expr (where the word expansion prefix {*} doesn't even have meaning).
It would not interfere with {prefix}bareword or {prefix}$variable or {prefix}[command] or {prefix}"string" or {prefix}{string}, with the rare exception of a bareword starting with an open parentheses. <</AMB>>
nektomk - 2026-02-25 19:52:25
what about other procs similar to expr? (mpexpr dexpr ). If the syntax of the language is expanded, they must also be taken into account. It would be strange to have a different notation for expr and mpexpr, or to have limited capabilities for multi precision calcs.
AMB - 2026-02-26 15:00:24
Re: FM
The only context in which a colon has meaning in a Tcl expression is with the x ? y : z format. I don’t think that would conflict with fully declared bareword variable access. But maybe it does. If so, you could make an exception that for those variables, you have to use the dollar sign. This exception is like how Tcl array access would also need the dollar sign to avoid being interpreted as a mathfunc. The proposed improvement to Tcl expressions to allow for barewords as variables necessarily has limitations. For every instance where a variable name conflicts with the expr language, you would have to use the dollar sign to be explicit.
I stand by my proposal of {commandPrefix}(arg,arg) for accessing any Tcl command in an expression.
<<FM>> To resolve the function / array ambiguity, I was planning to use a prefix :
set x [(R * cos(theta) + {array}A("Ox") )]<</FM>>
<<AMB>>
But why? That makes it so much more complicated. Variable access with the dollar sign in Tcl expressions must be supported, or you would literally break almost every Tcl script in existence. So, using the already existing syntax, you can do this:
set x [(R * cos(theta) + $A(Ox) )]
There is absolutely no need for an {array} prefix. It is completely redundant; a single dollar sign works just fine.
Even if you want to access an array at an index that involves a math calculation, you can just use your proposed array index expression shorthand $var((...)) to do the trick.
set i 2
array set A {0 4 1 20 2 12 3 5 4 8}; # array with indices 0-4
set x [(R * cos(theta) + $A((i+1)) )]
# set x [(R * cos(theta) + {array}A(i+1) )]; # this is still longer, and differs from normal Tcl array access.But frankly, I don't like using Tcl arrays for integer indexing, I think it is poor form. Tcl lists are much better for integer indices. Tcl arrays are unordered hashmaps, so it is harder to loop over them.
set element {list}$L(1)
set element {dict}$D(key)
set element {array}$A(key)
set element {struct}$S(field)
#... then we would be able to write
expr {
{list}$L(0} + {dict}$D(key) * {array}$A(key) + {struct}$S(field)
}So, instead of using a Tcl array, let's say you used a list, and created the common alias @ for lindex. Then, you could access a list in the following manner, with my {commandPrefix}(args) notation.
set i 2
set A {4 20 12 5 8}; # a list, with the same values/indices as the array in the previous example.
interp alias {} @ {} lindex; # common alias
set x [(R * cos(theta) + {@}($A,i+1) )]; As an aside, FM, I feel as though you are opposing my {commandPrefix}(args) proposal only on the basis that it might interfere with your proposed protocol concept, and it is frustrating. I think that my proposal has a lot of merit, and I don't feel as though you are giving it a fair appraisal.
I think that your [(...)] proposal as shorthand for [expr {...}] is brilliant. I am in full support.
That aside, there are a few main pain-points of doing math in Tcl, and addressing those in the simplest way should be priority.
The pain points I see are:
Shorthand for expr
Access to Tcl commands within expressions
Bareword variable access in expressions
Vector/matrix support
Vector/matrix operations
List expansion
Assignement operator
Multi-instruction expression
<</AMB>>
AMB - 2026-02-26 22:35:33
Hey FM, in regards to your prototype of the [(...)] shorthand for [expr {...}], could this idea also be expanded such that if any command starts with an open parentheses, it looks for the matching parentheses and then evaluates the body of the expression?
Then, you could do stuff like this:
# mapping over a list
set A {1 2 3}
set B [lmap a $A {($a*2)}]
# set B [lmap a $A {expr {$a * 2}}]; # current way
# proc that does math (would work exactly how you proposed near the top of this page)
proc dist {x0 y0 x1 y1} {(
hypot($x1-$x0,$y1-$y0)
)}And, if you are in an interactive shell, you can do math by just opening a parentheses. It would be a natural extension of the [(...)] shorthand.
<<AMB>> Not quite, I am saying that in general, (...) would be math evaluation, if and only if it occurs at the beginning of a command. So, either on the newline of a script, after a semicolon, or within square brackets, if the Tcl parser first encounters an open parentheses, it then looks for the matching close parentheses (in the same fashion as curly braces). The resulting string within the parentheses is then parsed as a Tcl expression. This would effectively replicate the explicit [(...)] syntax, but with more flexibility.
So, let's imagine that you got the assignment operator to work. Then, math assignment would look like this:
(x = 5); # same as "set x 5"
(y = 2*x); # same as "set y [expr {2*$x}]"But it would only work at the beginning of a command. Outside of that context, parentheses would not have any special meaning.
set (hello) world; # This would set the "hello" index of the blank array to "world"
regexp -inline -all (a|b|c) {The quick brown fox jumped over the lazy dog}; # returns "c c b b a a"
($x + 1) foo; # this would throw an error "unexpected word after math evaluation"If you want to do math evaluation for an input to a command, you would have to wrap the parentheses in square brackets.
lappend mylist [($x ** 2)]
I have added this to the list of proposals.
<</AMB>>
AMB - 2026-02-27 21:18:22
I picked a rather ugly Tcllib math function and decided to apply some of our proposed improvements to Tcl expressions:
Here is the code as it stands:
# ::math::geometry::findClosestPointOnLineImpl
#
# PRIVATE FUNCTION USED BY OTHER FUNCTIONS.
# Find the point on a line that is closest to a given point.
#
# Arguments:
# P a point
# line a line defined by points A and B
#
# Results:
# Q the point on the line that has the smallest
# distance to P
# r r has the following meaning:
# r=0 P = A
# r=1 P = B
# r<0 P is on the backward extension of AB
# r>1 P is on the forward extension of AB
# 0<r<1 P is interior to AB
#
proc ::math::geometry::findClosestPointOnLineImpl {P line} {
# solution based on FAQ 1.02 on comp.graphics.algorithms - but avoid the
# chain of pow( sqrt(...) ,2) for better precision (& performance).
# L^2 = (Bx-Ax)^2 + (By-Ay)^2
# (Cx-Ax)(Bx-Ax) + (Cy-Ay)(By-Ay)
# r = -------------------------------
# L^2
# Px = Ax + r(Bx-Ax)
# Py = Ay + r(By-Ay)
set Ax [lindex $line 0]
set Ay [lindex $line 1]
set Bx [lindex $line 2]
set By [lindex $line 3]
set Cx [lindex $P 0]
set Cy [lindex $P 1]
if {$Ax==$Bx && $Ay==$By} {
return [list [list $Ax $Ay] 0]
} else {
set Lsquared [expr {pow($Bx-$Ax,2) + pow($By-$Ay,2)}]
set r [expr {(($Cx-$Ax)*($Bx-$Ax) + ($Cy-$Ay)*($By-$Ay))/$Lsquared}]
set Px [expr {$Ax + $r*($Bx-$Ax)}]
set Py [expr {$Ay + $r*($By-$Ay)}]
return [list [list $Px $Py] $r]
}
}Note how this required a whole line of comments, in pseudo-code, explaining what was going on.
Compare that to the version of the proc listed below:
# Tcllib function, using some proposed expr enchancements.
proc ::math::geometry::findClosestPointOnLineImpl {P line} {
# solution based on FAQ 1.02 on comp.graphics.algorithms - but avoid the
# chain of pow( sqrt(...) ,2) for better precision (& performance).
lassign $line Ax Ay Bx By
lassign $P Cx Cy
if {Ax==Bx && Ay==By} {(
((Ax,Ay),0)
)} else {(
L2 = (Bx-Ax)**2 + (By-Ay)**2;
r = ((Cx-Ax)*(Bx-Ax) + (Cy-Ay)*(By-Ay))/L2;
Px = Ax + r*(Bx-Ax);
Py = Ay + r*(By-Ay);
((Px,Py),r)
)}
}Besides modernizing the code with the lassign command, the following Tcl expression syntax improvement proposals were applied:
Notice how with these improvements, you don't need to document the math with pseudo-code comments. It is clear as day.
AMB - 2026-02-27 22:57:09
Just another thought came to me on the topic of an assignment operator in Tcl expressions if multiline expressions were allowed. What if we had two separate assignment operators, one which sets the corresponding variable in the current scope, and the other which sets a local variable that only persists within the evaluation of the expression?
The regular assignment operator would just be =, while the local expression assignment operator could be := or something.
This way, you can define generic variables within an expression without worrying about overriding an existing variable or cluttering the variable space.
set x 5 set y foo (y := x + 1; y*2); # 12 puts $y; # foo
AMB - 2026-02-28 15:57:33
No, I wasn’t saying that barewords are only for local variables. I was saying that there would be two assignment operators, one local to the expression, and the other that affects variables outside of it.
But yes, this is a lot. The shorthand should take priority. I really think it should be modified though, such that eval {(..)} is equivalent to expr {..}. This would replicate the [(..)] syntax and extend it.
FM 2026 03 02 I integrated TIP 282, with a little modification (allow bareword as variable name on the left side of the assign '=' operator. I wrote a TIP. I'm asking for sponsorship in the Tcl core team. I'm waiting for an answer.
Wathever, now, we can write :
proc TensorialProduct {V U} {
lassign $V x y z
lassign $U u v w
return [( a11 = $x*$u; a12 = $x*$v; a13 = $x*$w;
a21 = $y*$u; a22 = $y*$v; a23 = $y*$w;
a31 = $z*$u; a32 = $z*$v; a33 = $z*$w;
(($a11, $a12, $a13),
($a21, $a22, $a23),
($a31, $a32, $a33))
)]
}
puts [TensorialProduct {1 2 3} {3 2 1}]
# {{3 2 1} {6 4 2} {9 6 3}}AMB - 2026-03-02 22:49:32
FM, that's fantastic. As a starting point, the [(...)] shorthand, the ability to do assignment in expressions with an equal-sign (with bareword allowed to the left of the = sign), the semicolon separator for multi-expressions, and the (x,y,z) native list notation truly makes Tcl math a lot easier, as you demonstrated. While there are many more improvements that can be made, these changes would definitely make life a lot easier.
<<FM>> Well, I'm happy, but it's still can be complicated... I wanted to make a proc for matrix product, which would work even if the second matrix is a simple vector : Here is what I got :
proc MatrixTranspose {M} {
if {[llength [lindex $M 0]] == 1} {
# M is a vector
return $M
}
set i 1
foreach row $M {
lassign $row m${i}1 m${i}2 m${i}3
incr i
}
return [( $i == 2 ?
(# it was a false matrix case : there was just one row after all.
$m11, $m12, $m13
) : (
($m11, $m21, $m31),
($m12, $m22, $m32),
($m13, $m23, $m33)
) )]
}
proc MatrixProduct {M1 M2} {
set i 0
foreach row $M1 {
lassign $row m${i}0 m${i}1 m${i}2
incr i
}
set R []
foreach v [MatrixTranspose $M2] {
lassign $v x y z
lappend R [( [llength $v] == 1 ?
(vector = 1; # M2 is a vector !
y = [lindex $M2 1];
z = [lindex $M2 2];
($m00*$x + $m01*$y + $m02*$z,
$m10*$x + $m11*$y + $m12*$z,
$m20*$x + $m21*$y + $m22*$z)
) :
(vector = 0; # M2 was a matrix.
($m00*$x + $m01*$y + $m02*$z,
$m10*$x + $m11*$y + $m12*$z,
$m20*$x + $m21*$y + $m22*$z)
))]
if {$vector == 1} {
return {*}$R
}
}
return $R
}
# Affine transforms examples :
proc translation {dx dy} {
return [( (1, 0, $dx),
(0, 1, $dy),
(0, 0, 1) )]
}
proc rotation {angle} {
return [( angle = $angle/180.0*acos(-1);
(cos($angle), -sin($angle), 0),
(sin($angle), cos($angle), 0),
(0, 0, 1) )]
}
set Point [( (100*cos(30.0/180*acos(-1))), 50, 1 )]
set RotatedPoint [MatrixProduct [rotation -30] $Point]
set TranslatedPoint [MatrixProduct [translation -100 0] $RotatedPoint]
puts RotatedPoint\ :\ $RotatedPoint
puts TranslatedPoint\ :\ $TranslatedPoint
# RotatedPoint : {100.00000000000001 7.105427357601002e-15 1.0}
# TranslatedPoint : {1.4210854715202004e-14 7.105427357601002e-15 1.0}
It's a full new ocean of possibilities... and it's working also in while, for, if,... Lot of wiki pages to illustrate...
But no news yet from the Tcl core team. Maybe they are not interessed by this work ? <<FM>>
<<AMB>> FM, I believe I've mentioned this before, but the [(...)] syntax should NOT return a list by default, or else it will not be a true alias for the expr command. A list would require an extra set of parentheses: [((...))]. I am not in support of it returning a list by default.
This is how it should behave, in my opinion:
proc translation {dx dy} {
return [(((1, 0, $dx),
(0, 1, $dy),
(0, 0, 1)))]
}
proc rotation {angle} {
return [( angle = $angle/180.0*acos(-1);
((cos($angle), -sin($angle), 0),
(sin($angle), cos($angle), 0),
(0, 0, 1)) )]
}
set Point [( ((100*cos(30.0/180*acos(-1))), 50, 1) )]<</AMB>>
AMB - 2026-03-03 15:21:47
I think we should try to join the next Tcl meetup and talk with people there about it. They might not have seen our discussion. I agree that this opens up a lot with Tcl, and would increase the popularity of the language. There is an Asia/Pacific one on March 10 and an Americas one on April 14 2026. See Monthly Virtual Meetup.
Also, FM, in regard to handling data structures such as vectors, matrices, and higher-dimensional tensors, I have been working on a pure Tcl package for a long time that has advanced indexing, mapping, and transformation of arbitrary rank tensors. It doesn't address the core issue of a lack of vector/matrix math support in Tcl expressions, but it provides a decent workaround. Here it is: https://github.com/ambaker1/ndlist
I'd be curious to hear your thoughts on it.
AMB - 2026-03-03 20:20:38
This is how I think that the proposed expr enhancements should behave to be consistent.
Shorthand Notation:
If the first character in a Tcl command is an open parentheses, it signifies math environment, and will look for a matching close parentheses, ignoring parentheses contained within quoted strings, braced strings, and square brackets. Newlines will be allowed within the math environment with no special meaning but whitespace. The string contained within the parentheses will be parsed as a Tcl expression, exactly as if it was passed to the expr command. If there are any characters after the close parentheses (i.e. another word parsed by Tcl), it will throw an error.
Example:
set x 5.0
set y [($x*2)]; # 10.0
eval {
($x + $y
) }; # 15.0
lmap value {1 2 3} {($value + 2)}; # 3 4 5
($x + 1) foo; # error, word encountered after math environmentVariable assignment:
Within the main expression, with the syntax var = ..., where var can be a bare word. This is not valid within a sub-expression.
Example:
(x = 5.0) (y = $x*2) (2*(z = $x + $y)); # error, assignment operator encountered in sub-expression
Multiple Statements:
Within the main expression, multiple statements can be written, separated by semicolons. This is not valid within a sub-expression.
Example:
( x = 5.0; y = $x*2 ) (z = ($x + $y;10.0)); # error, semicolon separator encountered in sub-expression
Native List Notation:
Within the main expression, parentheses denote a list where elements are separated by commas. Commas in the main expression are not allowed. Nested lists are also allowed.
The parentheses to enter into the math environment do not denote a list. This would deviate from the behavior of the expr command.
Example:
(
x = 5.0;
y = $x*2;
z = ($x+1,$y-5)
); # 6.0 5.0
(nestedList = (($x,$y),$z)); # {5.0 10.0} {6.0 5.0}
("hello world"); # hello world
($x,$y,$z); # error, comma encountered in main expression.FM, with these rules in mind, your example proc for matrix product would be written as such (with a few fixes):
proc MatrixProduct {M1 M2} {
set i 0
foreach row $M1 {
lassign $row m${i}0 m${i}1 m${i}2
incr i
}
set R {}
foreach v [MatrixTranspose $M2] {
lassign $v x y z
lappend R [if {[llength $v] == 1} {(
vector = 1; # M2 is a vector !
y = [lindex $M2 1];
z = [lindex $M2 2];
($m00*$x + $m01*$y + $m02*$z,
$m10*$x + $m11*$y + $m12*$z,
$m20*$x + $m21*$y + $m22*$z)
)} else {(
vector = 0; # M2 was a matrix.
($m00*$x + $m01*$y + $m02*$z,
$m10*$x + $m11*$y + $m12*$z,
$m20*$x + $m21*$y + $m22*$z)
)}]
if {$vector == 1} {
break
}
}
return $R
}<<FM>> AMB, I can't see how your proposal can be done. <</FM>> <<AMB>> FM, when I get some time to set aside for this, I'll put together a prototype. <</AMB>>
arjen - 2026-03-05 08:20:06
The page is already pretty lengthy, so I will try to keep my comment short. In Fortran (since the Fortran 90 standard) you can define custom operators. These can be the usual operators that are then applied to different data types - the precedence remains the same - or completely new ones. The latter are notated as: x .name. y for binary operators or .name. x for unary operators. The "name" part is something a programmer picks themselves. I have used this mechanism for instance for defining spatial gradients, .grad. (nabla). The precedence for these operators is straightforward: unary operator come before all others and binary operators come after all others. This mechanism might be built into Tcl too, if we are revising the working of expr.
<<FM>> That's close to my idea of protocol on operators. In fact, binaries + or * or ** could be any proc (with 2 args, of course). All what really matter is their precedence rule. If we could map alternative procs on operator, we would extend a lot the power of expr. expr would then appear not only as specific arithmetic evaluator, but as a very generic parser of infix expression. That's why I was proposing to use configurable prefixes. Ex : expr { V = $nabla {dot}* $x + $nabla {cross}* $v } . <</FM>>
AMB - 2026-03-05 17:23:07
I really like the .name. option for custom infix operators.
expr {$x .d*. $y}; # dot product of $x and $y
expr {$x .c*. $y}; # cross product of $x and $y
expr {$x .m*. $y}; # matrix product of $x and $y
expr {$x .k*. $y}; # kronecker product of $x and $y
# dynamic definition of operators
set op d*
expr {$x .$op. $y}; # dot product of $x and $yAlso, this would free up the braced prefix notation to allow for my proposed {commandPrefix}(arg,arg...) notation.
I am in favor of it, especially because it already has a precedence in Fortran.
nektomk - 2026-03-07 10:50:42
Now I believe that expr should be kept unchanged. There should also be no special innovations extending tcl in the {*} style. This will break compatibility and complicate things a lot.
But scalar and vector procedures can be implemented (the names are conditional):
scalar {single_expression}
vector {one_expression} { second_expression} ...returning a single value or a list of results, respectively
both procedures should:
procedures can save (and use in the future) their internal state (the value of their local variables, constants, functions, meta-data). For example, in a dictionary that is passed as a separate parameter or a push/pop context. And the user can configure/adjust it and, of course, read the side effects.
Obviously, a separate string-substitutions mechanism must be implemented inside scalar|vector expressions.
it is desirable to be able to choose the expr/mpexpr/dexpr/rational/other basis.
A kind of sub-language :-) But the requirements are reasonable in my opinion.
<<FM>> The changes I propose are backward compatible. Now, I've implemented the {(...)} option. A script which begins with a ( and finish by a ) will be compiled as an expression. It works in foreach, lmap, for, while, eval, apply, proc, bind ...etc. To test it I've program a proc to do 3d Matrix Inversion :
proc comatrix {M} {
set MAP {{a b c} {d e f} {g h i}}
lmap row1 $MAP row2 $M {
lmap e1 $row1 e2 $row2 {($e1 = double($e2))}
}
return [( ($e*$i - $f*$h, $f*$g - $d*$i, $d*$h - $e*$g),
($c*$h - $b*$i, $a*$i - $c*$g, $b*$g - $a*$h),
($b*$f - $c*$e, $c*$d - $a*$f, $a*$e - $b*$d) )]
}
proc transpose {M} {
set MAP {{a b c} {d e f} {g h i}}
lmap row1 $MAP row2 $M {
lmap e1 $row1 e2 $row2 {($e1 = double($e2))}
}
return [( ($a, $d, $g),
($b, $e, $h),
($c, $f, $i) )]
}
proc Inverse {M} {
set TCOM [transpose [comatrix $M]]
set det [det $M]
if {$det == 0} {error "matrix non inversible"}
return [lmap row $TCOM {
lmap e $row {(double($e/$det))}
}]
}
puts [Inverse {{1 2 3} {5 5 2} {5 6 7}}]
# result : {-1.9166666666666667 -0.3333333333333333 0.9166666666666666} {2.0833333333333335 0.6666666666666666 -1.0833333333333333} {-0.4166666666666667 -0.3333333333333333 0.4166666666666667}<</FM>>
AMB - 2026-03-09 14:27:52
FM, this is great, but does calling just \(...) (at the start of a command/script) result in math evaluation? I feel like restricting this to the exact string of characters [(...)] and {(...)} is limiting, and could be handled in a more general way.
<<FM>> To do so, I should have add ) as terminator in Tcl_ParseCommand, and change its behaviour more deeply. I'm affraid about the consequences of such a transformation on the users, because Tcl is very permissive with what we can write, that's why I prefered to rely on these two-chars symbols :
Maybe, in the future, unicode chars could be allowed ?
But writing them is not easy.
I'm actually implementing command prefix :
namespace eval dict {
proc get args {
tailcall ::dict get {*}$args
}
}
set D {A 9}
puts RESULT:[expr {1 + {::dict}get($D, "A")}]; # RESULT:10<</FM>>
KSA I'm late to the party, just wanted to add my thoughts:
I do understand that expr can be annoying sometimes. I once ported a cryptographic hashing function for Tcl and it felt a very unpleasant thing to do. However I'm also worried Tcl language changes too much by some proposals and we would end up with some kind of "Frankenstein language", which will not only attract no new users but even will disgust existing Tclers. My feeling is that something small like $( ... ) might still be okay because it doesn't change the "Tcl feeling" much. But I'm more sceptical on some other proposals that change the language in radical ways. On the other hand Tcl can do similar things today already, consider:
proc = {args} { return [expr $args] }
set y [= 4+5]This feels good already. Of course the space after "=" is mandatory but even this can be changed with what we have today, consider:
proc = {args} { return [expr $args] }
rename ::unknown ::_unknown
proc ::unknown {args} {
if {[string index $args 0] eq "="} {
return [expr [string range $args 1 end]]
} else {
return [::_unknown {*}$args]
}
}
set y [=4+5]Need list results? Here we go:
proc ::tcl::mathfunc::list {args} { return $args }
proc = {args} { return [expr list($args)] }
rename ::unknown ::_unknown
proc ::unknown {args} {
if {[string index $args 0] eq "="} {
set x [string range $args 1 end]
return [expr list($x)]
} else {
return [::_unknown {*}$args]
}
}
set y [=4+5, 3*7, 123]To implement we would need to define things as being language default and of course there're some things we can't do any longer, like defining proc starting with = character. Plus the expr environment needs to support lists by syntax (1,2,3,...) to make nested lists working, e.g.
set y [=(1,2,3),(9,8,7)] ;# -> error, but should work: nested lists!
When it comes to math the biggest drawback of Tcl in my POV is the lack to support vectors/lists operators in expr and it's that missing feature that prevents us/me (I've tried!) from adding smooth vector math implementations. Unlike math functions that can be added and replaced via namespace ::tcl::mathfunc:: the namespace ::tcl::mathop does not work this way: The functions in there can be called for equivalent functionality as their counterpart operators in expr environment, but replacing or adding functions in there does not change expr operators.
Without being able to replace operators attempts to emulate vectors boil down to something like
expr {add(vector(1,2,3), vector(1,2,3))}But the more intuitive syntax fails and can't be emulated
expr {vector(1,2,3) + vector(1,2,3)}In this regard MATLAB/Gnu Octave and even Python come much smoother today: That's a pity because Tcl has strong list support but fails to run computations with the data they contain in a smooth way. A typical task I'm often confronted with (and I usually go for Octave to solve it) is running some simple statistics on measurement data, e.g. correcting some drift or offset and then computing standard deviation, average, median and so on. In Octave that's more or less a one liner while in Tcl it would need loops and list manipulation functions.
FM Hi, KSA, AMB. The prototype implementation of my ideas can be found at https://github.com/florentis/tcl90-exprSH/tree/core-9-0-branch/install_SH/bin
It allows inline expr shorthand [(...)] , array index expr shorthand : Array(($i+1)), and script expr shorthand, which can be either {(...)} or "(...)" or even (...), all what really matters is that the word must be in a position to be compiled, begin by a ( and finish by a ')`
I added to this, some improvement into expr, assignement of variable, and separator from tip 282. My self I created a list capability with this syntax (1,2,3).
I don't see any problem to create a sommation of list later, since expr now can recognized a list. My problem is more with multiplication, since a lot of distinct products exist for vectors. Contracted ? Not contracted ?
You may have a try and give me feed back.
Even if, in fact, Tcl can still be used the usual way, the core team seems to be a little afraid by this syntax addition.
AMB - 2026-04-10 19:11:40
Hi FM, I took a break from this, but it is good to hear that there is more progress. I'm happy to hear that the shorthand works for just (...), and I am excited to download and try out your implementation!<FM>see below<FM>
However, I disagree with the "(...)" notation. If you want to use quoted string subsitution, I think that the notation should be "[(...)]". The reason for this is that when you write puts "set x 5", it doesn't evaluate the command set x 5. Similarly, if you wrote puts "(x = 5)", I do not think that it should evaluate the expression.
<FM> answer : puts "(x = 5)" is not evaluated as an expression. It is difficult to explain, because a script, in Tcl, is not only defined by its [ and ] delimiters in the Tcl Parser. A script is also defined by its position in a command. For instance, eval, proc, apply, if, for, foreach, while, switch, ..., all these commands define some of their arguments as script. In these situation, the delimiters - only meaningfull at the substitution step, and removed before compilation, play no role at all. That's why you can write either : eval {...} or eval "..." or even eval ... : That is the command wich may decide to define this argument is a script.
In the example you give, puts "set x 5", the puts command doesn't define its second argument as a script, so it isn't asking for compilation of it. When you write puts "(set x 5)", there won't be any expression substitution, you will just get "(set x 5)" on the output channel. Now, going back the eval example above : eval defines it's only argument as a script (or the concatenation of them if many), and clame for compilation of it. In such a situation, you can write either eval "(...)" or eval {(...)} or even eval (...) and finally get the script evaluated as an expression. It's complex to explain, because there is a need to know the Tcl internals to get it : The parser delimiters doesn't define the nature of an object, but the nature of a substitution. </FM><br>
Additionally, as I have stated before, I disagree with having the expression shorthand automatically create a list. This will break existing code. Instead, the (...) shorthand should simply be shorthand for expr {...}, and then within an expression, the (x,y) notation would be shorthand for a list. So, if you wanted to create a vector with the notation, you'd do the following:
set x 5 set y 3 set myvector [(($x+1, $y+2, $x*$y, $x+$y))]; # 6 5 15 8 # set myvector [($x+1, $y+2, $x*$y, $x+$y)]; # this should return an error
<FM> answer : It doesn't create a list automatically. let me explain how I implemented it : While parsing, before each parenthese, ParseExpr create a "NULL_FUNC" node. The only situation where this NULL_FUNC node is translated into a FUNC node (with "list" added into the list of function) is when a "comma out function error" occurs. In the next step, while compiling the expression, any NULL_FUNC node is discarded, and so doesn't generate any list bycode. So, "no comma out of function argument" error, no list. By default, neither expr, neither the shorthand create a list. You can write :
set x [(1+2+3)]; # no comma, no list
set x [(((((1+2+3)))))]; # no comma, no list
set y [(1,2,3)]; # commas, so list
set x [expr {(1+2+3)}]; # no comma, no list
set x [expr {((((1+2+3))))}]; # no comma, no list
set x [expr {(1,2,3)}]; # commas, so list
set x [expr {1,2,3}]; # unexpected "," outside function argument list</FM> The way I imagine this working is that it would establish two levels to expressions: primary and secondary.
<FM> I don't like the idea to have distinct rules on distinct levels at all. I don't think is doable. There is one parser. It must works identically everywhere. The notion of level of parentheses is absent from the expression parser. Parenthese are taken as unary operator there.</FM><br> So, with the variable assignment operator, the example I provided can be written as follows:
( # my program x = 5.0; y = 3; myvector = ($x+1, $y+2, $x*$y, $x+$y) ); # 6 5 15 8
<FM - Answer : this doesn't work. When it will go through Tcl_ParseCommand, it will stop at the first new line and complain that "(" is an unknown command. Rules of parsing are really differents between commands and expressions. Maybe it is feasible, with backslash-newline sequences. For now, I have made it another way :
proc {} {} {}
[( # my program
x = 5.0;
y = 3;
myvector = ($x+1, $y+2, $x*$y, $x+$y); )]When you end your expression with a ;, it returns {}. Defining the {} proc as a no-op allow to include expression in any script like this.</FM>
</FM>
AMB - 2026-04-13 19:37:18
FM, thank you for explaining a bit more about how your comma-separated list notation works.
I have a proposal that should resolve the discrepancy that I see:
The issue I have is that expr {"hello world"} returns hello world, while expr {"hello world","foobar"} returns {hello world} foobar. With the way that you have set things up, there is no clear way to return a one-element list.
<FM> Answer : There is no clear distinction in Tcl about list / string. The same value can be taken either as a list or as a string. Lists are not strongly typed. You can't distinguish between singleton list and string. Even one simple string, but with with space in it, can be interpreted as a list of many elements. I wrote on this subject in Russel Paradox section of the page Tcl's Popularity : There is no clear distinction between elements and collection in Tcl. Here you have a singleton list with one string element which contain spaces. It can be taken by Tcl as a list of two words.</FM>
The solution, as I see it, is to make it so that a leading comma or a trailing comma denotes a one-element list. Then, expr {"hello world",} or expr {,"hello world"} would return {hello world}. The trailing (or leading) commas would be ignored; it would just flag them as a list.
<FM> Why not just enclose your list with two extra braces between the quotes ? set L [expr {"{hello world}"}]; lindex $L 0; # "hello world" . </FM>
Furthermore, generalizing this, the rule would be that if there is just whitespace before the first comma, after the last comma, or between commas, those list elements would be discarded. So, expr {,"hello world",,, , ,,"foobar",, ,} would just return {hello world} foobar, in the same way that additional whitespace between elements in a Tcl list gets discarded.
<FM> Well, I don't like it that much. I don't think synctactical trick can allow us to get out of the Russel Paradox : (Tcl tried this already, with annoying corner cases). It's more a matter of semantic. My thought is instead to be able declare, in expr, that 'this' is collection, then to allow using the array notation to access individual elements of the collection, the varname beeing written as a bareword. Ex :
[( L is List ; L = "hello world" ; s = L(0); )]; puts $s ;# "hello world"
[( D is Dict ; D = "hello world" ; s = D("hello") ; )]; puts $s; # result in "world"
[( S is String ; S = "hello world" ; s = S(0); )]; puts $s ;# results in "h" : a string is a collection of chars, whose first is "h"
[( M is Matrix(3x3); M = ((1,2,3),(4,5,6),(7,8,9)); a = M(0,0); )]; #...etc;</FM>
With this in mind, let's say that the asterisk operator is implemented as a unary operator for expanding a list (which is one of my wishlist items). Then, expanding an empty list would not cause an error, as shown below:
# expanding a vector of length 3
set myvector {1 2 3}
expr {*$myvector,"hello world"}; # 1 2 3 {hello world}
# expanding an empty vector
set myvector {}
expr {*$myvector,"hello world"}; # {hello world}AMB - 2026-04-13 19:44:55
If it is too difficult to implement different levels in the Tcl expression parser, then I think that the leading/trailing comma approach is a good compromise that doesn't break existing Tcl code.
Regarding the (...) shorthand notation, I really think it needs to get implemented in a way that resolves all the syntaxes, from {(...)} to [(...)] to just (...). I would imagine that this is possible. If the Tcl command parser first encounters a parentheses after leading whitespace, it looks for the matching parentheses. No need for special cases for the syntax [(...)], {(...)}, or "(...)". If you can handle the most general case, it will cover all the other special cases.
<FM> : There is a lot of entrance points to make a parsing as well as a compiling. When you use {...} or "..." or [...] delemiters, you are defining a kind of substitution that Tcl will apply, not objects. The [...] delimiters are more constants : we know immediatly it will be compiled at the end. But words enclosed with braces or quotes, or even simple words can be compiled also, if the command which recieve them clame for it. A script is not only defined by delimeters. At least, first we must be sure we cover all of them. Maybe in a second step, we can unified them, but I'm not at this point yet. For now, I can distinguish three main cases :
AMB - 2026-04-14 13:52:41
FM, yes the everything is a string paradigm introduces unique challenges, but it is Tcl. That is what Tcl is.
If list expansion is introduced within comma-separated lists (or mathfunc arguments), then the comma syntax I introduced must be valid for it to work with null lists. It would also provide a method to return a one-element list. Whether you like how it looks or not, it would be necessary for list expansion in expressions.
FM - answer Russel Paradox demonstrates a radical impossibility : you can't use exactly the same logic with collections and elements, if you try, it can never be coherent. Tcl can't challenge a proof that was exposed more than one century ago. Tcl must do with it and take into account. It's not the case yet. Try :
% set R [list]
% if {$R ni $R} {puts "Russell's Paradox !"} else {puts "Russell's paradox"}
Russell's Paradox !On the left, $R is taken as an empty string, whereas, on the right, $R is taken as the empty list.
% set R "hello world"
% expr {$R in $R}
0
% expr {$R ni $R}
1Of course, that can be explained on these simple cases. But if ever you have many level of substitution, the results are becoming quickly unpredictable.
AMB - 2026-04-15 18:17:57
FM, I've read your posts and comments on Russel's paradox. I have never ran into an issue with it personally, and I don't see how it is relevant to this discussion.
My point remains - if commas denote returning a list in a Tcl expression (or subexpression), then there must be a way to denote a one-element list in the case of elements that can be parsed as lists themselves. The simple solution, as I see it, is to permit leading or trailing commas. I prefer the look of a trailing comma personally.
Example: trailing comma to denote returning a one-element list from an expression.
set x {1 2 3}
expr {$x}; # 1 2 3
expr {$x,}; # {1 2 3}This would remove ambiguity, ensuring that the result is a one-element list, if that is what is the desired output.
Also, upon further reflection, I take back what I said about having to also allow for leading and back-to-back commas. Instead, I only propose that a trailing comma is an optional flag to return a list. This should be easy to implement.
Example: trailing comma is optional
expr {"hello world",foo,bar}; # {hello world} foo bar
# with optional trailing comma
expr {"hello world",foo,bar,}; # {hello world} foo barFM - 2026-04-16 00:15:57 Here is why it is relevant. Try :
% set withspace "hello word"
% set withoutspace "hello"
% expr {$withspace in $withspace}
0
% expr {$withspace ni $withspace}
1
% expr {$withoutspace in $withoutspace}
1
expr {$withoutspace ni $withoutspace}
0Conclusion : There is no unambigous list object in tcl. Any string with space in it can become a list, if some command ask for it. Likewise, any list can become a string. expr won't have this power to decide what the command which will recieve its result will do with it.
In your example :
The braces won't allow you to define : "this is a list object", but to define : this is a text where I don't want any substitution.
But, if you want to define something which can be taken as a singleton list in the most common situations, you can write : expr {"{hello world}"} / set A "hello world"; expr {"{$A}"}
Also, I can notice this syntax seems to remove the ambiguity shown above.
% expr {$withoutspace in "{$withoutspace}"}
1
% expr {$withspace in "{$withspace}"}
1AMB - 2026-04-16 14:24:35
Yes, expr {"{hello world}"} does return {hello world}. But why can't we also have expr {"hello world",} also return {hello world}? That is what I am proposing, that a trailing comma in an expression turns the result into a one-element list. To make it more general, just have a trailing comma be optional.
If we are making it so that parentheses now have the added functionality of generating a list, this will help resolve the ambiguity.
Also, yes, wrapping the right-hand side of in with "{...}" or [list ...] does resolve ambiguity in the case of the empty list/null string.
set x {}
expr {$x in $x}; # 0
expr {$x in "{$x}"}; # 1
expr {$x in [list $x]}; # 1FM -- As a comma is mapped as a binary operator, a trailing comma implies some work on the expr parser. Doesn't know if this effect you like is possible.
But my priority is to resolve an annoying bug with array index.
I was making some tests. With scalar variables, everything was working as expected :
# set w [expr {[set u 3]*2}]
[(w=(u=3)*2;)]; # nested assignement : ok
# set w [set old $w; return 0]
[(w=(old=$w; 0);)]; #save old var value and reset : ok
# set w [set tmp $w; return $u]; set u $tmp
[(w=(tmp=$w; $u); u=$tmp;)]; # swap two vars : ok
# set [set ref var] "referenced"
[((ref="var")="referenced";)] ;# ref to a variable : okBut when came the time to test array capabilities, things showed bad :
set A((2+2)) 4;# working as expected
[( "A([(1+1)])" = 2;)]; # working as expected
[( i=2; "A([($i+1)])" = $i+1;)]; # working as expected
# but :
[("A((2+2))"= 4;)]; # bugThe error is TclStackFree: incorrect freePtr (00000000014E9C30 != 00000000014E9C1F). Call out of sequence? Illegal instruction
Obviously, there is some problem in memory management. I'm still trying to understand how Tcl is working on this matter to see if it is possible to get it correct. Any help is welcome.
AMB - 2026-04-17 21:33:42
I have no doubts that it is possible to have the trailing comma in lists. It may require some creative solution, but I see no technical reason why it can't be done.
If there are serious technical issues with the $arr((...)) notation, perhaps that could just be a feature that is added later? I think that the (...) notation in the context of command evaluation takes priority. Calling $arr([(...)]) is still a lot shorter than $arr([expr {...}]).
FM About the singleton list, I'm still not convinced it can be usefull. I think the command interface is better to handle list, because neither commas, neither quotes are needed in this context. Could you give me an example where singleton list in expr is absolutely essential ?
About the arr((...)) notation (index expr shorthand), you convinced me that it is not a priority. There is technical issues, because the parsing of the array index can occur during the execution (in PushVarname or with TclLookUpArrayElement). As the only way to substitute an expression is to use the execution engine, things get very complicated, because there is the at least 2 stacks to care of, the stack for object, and the stack of bytecode, the order of operation is important, the memory stacks shall be alloced and freed in exact reverse order. There we get in the deep internals of Tcl, and I'm not competent in these areas.
I think the name part and the index part of a variable should be detected a lot earlier. During execution, it's very late. This part of the Tcl parser should be improved. I got an idea to change things on this :
Yes, it's a full big change of it's own.
Moreover, the index array shorthand is not as much usefull : If we need to compute an index as an expression, it will be easy, and more clear, to use a variable.
[(i = $j*2; "arr($i)" = $j+3)]
So, I will just remove this part of the TIP.
Bye, bye index expr shorthand. Thank you to make me realize this.
AMB - 2026-04-20 14:55:12
Hi FM, I guess it isn't strictly necessary for singleton lists to be able to be returned with the improved expr notation. It should just be clear in the documentation that an expression without commas returns the full value, not a singleton list.
I agree that it isn't strictly necessary at this point, and would only really be an issue if Tcl operators worked on vectors. Even so, there are other ways we could address the singleton list issue.
In a future TIP, we could either implement the trailing comma as I proposed, or the following proposals would also address the issue:
1. Let any Tcl command be accessed in a Tcl expression with the notation {command}(arg,...). I know you have reservations about this, but for the sake of the discussion:
[( x = {1 2 3}; {list}($x*2))]; # {2 4 6}This effectively works as "tagging" the expressions with a type, as you have proposed before.
proc matrix {value} {
# Check that dimensions are valid
set m [llength [lindex $value 0]]
foreach row [lrange $value 1 end] {
if {[llength $row] != $m} {
return -code error "inconsistent row length"
}
}
return $value
}
[( x = {matrix}((1+5,4*3),(2+2,8.0/4)) )];2. Let the operator * have a unary form that expands a list. This is useful in its own right, but would also provide a way to do a singleton list.
# Target use-case for unary * operator
[( x = {1 2 3}; max(*$x) )]; # 3
# Creative use for returning a singleton list
[( x = {1 2 3}; ($x*2,*{})]; # {2 4 6}Both of these potential improvements could address the issue, so the trailing comma idea is not strictly necessary. I rescind my position on it.
Also, I agree with you that the shorthand index expression notation is not strictly necessary. I have zero issues with it, but from what you said, it isn't enough of a convenience to justify such large changes to the Tcl core.
Keep up the great work FM!!
FM - 2026-04-21 21:07:12 Thanks for your encouragements, AMB. I still have to put things in order on the repository. Yes, once expr would be multi-instruction, a lot of new kind of proposals can be made. As the expr parser is more strict (ex : the = operator can't be renamed, contrary to the set command), I think that maybe it could be more easy to generate native code from it (aka : compilation in C). Then we could have this model :
It would be very complementary ! To take the example of a matrix, as you gave above, it could be stored as an C-array of numbers to be fast. So, I don't think a proc, which is a command, is the good solution for it. Morover, because a matrix is a type of data field (and more pecisely : a collection of data fields), there is more than one thing to define :
All this should be communicated to expr.
So, one proc won't be enough. It's more like a collection of procs we need (critcl cproc ?). Maybe in one namespace ?
I know, I proposed to "tag" values with braced-word, to define their type. What is annoying is, as expr is not sensitive to space, braced-prefix will be more difficult to implement in expr. Maybe it's the solution, so let's keep it in mind. But I explored an alternative below.
To sumerize the idea :
# type definition example (for usage in expr : expr will )
# ------------------------------------------
namespace eval ::tcl::mathtype::matrix {
variable parameters
namespace eval cast {
namespace eval from {}
namespace eval to {}
}
namespace eval binary {
foreach op {+ - * / ** = ==} {
namespace eval $op {}
}
}
namespace eval unary {
foreach op {+ - func} {
namespace eval $op {}
}
}
}
proc ::tcl::mathtype::matrix::define {Var args} {
cdef {
typedef enum { INT, FLOAT, DOUBLE} Type_of_Num;
typedef struct Num {
Type_of_Num type
union {int i; float f; double g;}
}
}
set s {}
foreach e args {
if {![string is int]} {error "dimension must be integer : impossible to define this matrix"}
append s \[$e\] ; #
}
set parameters $s
if {[llength $Var] > 0} {error "no space in var is allowed"}
# define the Matrix as a multi-dimensional array of Num
cdef "Num ${Var}$s" ; # ex : Num M[3][3]
}
cproc ::tcl::mathtype::matrix::cast::from::list {Tcl_Obj *list} Matrix {... }
cproc ::tcl::mathtype::matrix::cast::from::string {Tcl_Obj *string}
cproc ::tcl::mathtype::matrix::cast::to::list {Matrix M } Tcl_Obj* { ... }
cproc ::tcl::mathtype::matrix::cast::to::string {Matrix M } Tcl_Obj* { ... }
cproc ::tcl::mathtype::matrix::binary::=::{} {M0 Obj} Matrix { ... }
cproc ::tcl::mathtype::matrix::binary::+::{} {M0 M1} Matrix { ... }
cproc ::tcl::mathtype::matrix::binary::*::{} {M0 M1} Matrix { ... }
# ...etcI think this is the minimum set of procs to be defined.
To use it, we need at least :
# Usage example
set M1 {{1 2 3} {4 5 6} {7 8 9}}; # line 1 : M1 is defined as a string
set res [(
(M2 is matrix(3,3)) = ((9, 8, 7), (6, 5, 4), (3, 2, 1)); # line 2
M3 = M2 + ($M1 as matrix(3,3)); # line 3
M3 {scalar}* 2
)]; # res is : {{20 20 20} {20 20 20} {20 20 20}}Explanation :
AMB - 2026-04-21 20:14:15
I don't mind the "as" operator to denote type, if you want to be more explicit within a Tcl expression. I also like the two-fold balance of dynamic scripting and more hard-coded calculations that could be made possible with the expr sublanguage.
With that in mind, I think that custom operators should use the .name. syntax, not a braced prefix. It is what FORTRAN uses, so it already has precedence with a popular language for numerical processing, and I think that the braced prefix has a better use case as a way to access any Tcl command in a Tcl expression.
# Access "lindex" within a Tcl expression
set x {1 2 3}
puts [(i = 1; y = {lindex $x}($i+1))]; # 3# Increment a variable
puts [(a = 2; {incr a}($a * 2))]; # 6Basically, it would act just like a mathfunc, where the arguments are parsed as Tcl expressions, but whatever is in the curly braces is interpreted as the prefix.
So, it would essentially convert {lappend x}($y + 5, $z * 10) into [lappend x [expr {$y + 5}] [expr {$z * 10}].
I really want this added!!
FM : Actually you can do, with my prototype :
set x {1 2 3}
puts [( i = 1; y = [lindex $x $i+1] )]; # 3 : call lappend command in expr context : working because of lindex calculation
puts [(j = (i = 1) + 1; y = [lindex $x $j] )]; # 3 : you can store your calculation into a variable
puts [( i = 1; y = [lindex $x [( $i+1 )] ] )]; # 3 : call lappend command in expr context, then call expr shorthand
puts [(a = 2; [incr a]; $a * 2)]; # 6 : simplest way : just call incr command in expression context
lappend X [($y+5)] [($z*10)]; # call the expr shorthand in command context
lappend X {*}[($y+5, $z*10)]; # call the expr shorthand in command context
[( a = $y+5; b = $z*10; [lappend X $a $b] )]; # set vars then call the lappend command in an expression context
[( ...; [ lappend X [($y+5)] [($z*10)] ]; ... )]; # call lappend command in expression context, then call expr shorthand in sub-command context
[( ...; [ lappend X {*}[($y+5, $z*10)] ]; ... )]; # call lappend command in expression context, then call expr shorthand in sub-command contextIsn't that enough ? The problem is to get the things compiled in the right order. It's already the case when the command is between bracket. How can we do this with an unary braced-prefix ? Maybe possible. Surely complicate to get it right...
But I won't work on it, because the way it is now can cover every needs I think :
We can transfert data from one context to another in two ways :
AMB - 2026-04-22 18:34:19
I guess it is fine for now, the shorthand does really clean things up. So while it may be nice, I guess it isn't strictly necessary at this point.
I think the only thing that would really make things complete here is to add the * unary operator for expanding arguments in an expression. This would complement the "brace-star-brace" expansion prefix in command evaluation.
AMB - 2026-04-22 19:47:30
I downloaded your prototype, FM, and it's great!!
However, there is one thing that doesn't work as I expected. If I just open up tclsh and type "(2+2)" and enter, it returns 4. But if I type " (2+2)" or "(2+2) ", as in whitespace before or after the expression, it throws an error. I believe that for this to be complete, the expression shorthand should ignore whitespace before and after the open and close parentheses.
How I think it should work:
(# my program
x = 5;
y = $x*2
) ; # This should be valid. The parentheses indicate an expression environment, only at the beginning of a command. Whitespace before and after are ok.
set x [ ( 2 + 2) ]; # again, whitespace should be allowed before and after the parentheses.
puts [info complete {(1 + 1}]; # this should return false, because there is an open parentheses.Other than that, it really works well, and it is amazing to see this finally come together!!
AMB - 2026-04-23 17:11:47
Another idea for improvement to Tcl expressions: The index operator @.
Here is my idea:
Let the symbol @ be an operator that performs "lindex" and "lset", depending on whether it is followed by an assignment operator. Also, instead of "end+-integer" notation, negative index notation (-1 means end, -2 means end-1, etc.) is used. This is so that the values passed are completely numeric. One side effect of this is that it would not permit expanding the list as can be done with lset, or getting a value outside of the size of the list. It would be a more strict version of list indexing.
Access notation:
value @ indexlist
Assignment notation
varName @ indexlist = value
# Examples (with shorthand notation)
set x {foo bar baz}
(x @ -1 = {hello world})
puts $x; # foo bar {hello world}
puts [($x @ (-1,1))]; # worldThis feature would largely replace "lindex", which I view as another one of Tcl's warts.
More examples:
set x [lindex $mylist 0 1]; # command style (x = $mylist@(0,1)); # expression style lset mylist end-1 foo; # command style (mylist@-2 = "foo"); # expression style
Going further, I would also like to see range indexing permitted, like getting a portion of a matrix. The lindex and lset commands work on values contained within a list, they do not permit getting a range of values, that has to be done with the lrange and lreplace or ledit commands.
FM :
In expr, any operator is tied to its own instruction (+ is INST_ADD, - is INST_SUB,...etc). @ would then be tied to INST_LINDEX_MM.
The limitation of this is that it exists many collection types. There is list. There is also dict, array. You have ndlist. There may be more specialized numerical type of collection too in the future.
But it wouldn't be possible to have one operator by collection type. The instruction "Get the part of a collection" is not. There is INST_DICT_GET for dict, INST_LIST_INDEX / INST_LIST_INDEX_MULTI / INST_LIST_INDEX_IMM for list / INST_LOAD_ARRAY for array.<<br> If we want to have an operator "get part of a collection", we need to know the type of collection we are dealing with.
That's why I think there is a need to declare the type of a variable. For this, there is two alternatives :
Then we can make depend the instruction of the @ operator on the variable type.
Each alternative is of equal right and can be explore. I prefer to explore the "binary operator declaration" alternative.
[( L is List = (1,2,3); a = $L @ 0;
D is Dict = ("hello", "world"); b = $D @ "hello"
;)] But what will return this operation L is List ? Two things can be returned : either the varname, either the type. It is interesting to return the varname, since we can then write (L is List) = (1, 2, 3) and set the variable L. But it can be also convenient to return the type, if we want to work with ontologies. Ex : (human is (animal is (being which move))). This two possibilities imply two distinct operators :
Furthermore, to retrieve the part of a collection, it already exists a way to do it today : The array parenthesis syntax. If we use a generalized conception of it we would have :
[( L as List = (1,2,3); a = $L(0);
D as Dict = ("hello", "world");
b = $D("hello")
;)] It would work, since the "as" operator has recorded the varname "L" to be of type "List". I would prefer this last solution, since it makes Command Interface and Expression Interface behave identically.
It is of course impossible now :
But there is TIP 29 which propose to generalize this syntax to list as well. There is an interesting example inside. You can refer to the TIP to get the context. Let me show how it could become with the idea I'm exposing now :
# original non optimized
proc shuffle:O(n²) { L } {
set n [llength $L]
for { set i 0 } { $i < $n } { incr i } {
set j [expr {int(rand()*$n)}]
set temp [lindex $L $j]
set L [lreplace $L $j $j [lindex $L $i]]
set L [lreplace $L $i $i $temp]
}
return $L
}
# original optimized
proc K { x y } { set x }; # K combinator
proc shuffle:O(n) { L } {
set n [llength $L]
for { set i 0 } { $i < $n } { incr i } {
set j [expr {int(rand()*$n)}]
set temp1 [lindex $L $j]
set temp2 [lindex $L $i]
set L [lreplace [K $L [set L {}]] $j $j $temp2]
set L [lreplace [K $L [set L {}]] $i $i $temp1]
}
return $L
}
# Shorthand + Index access whith parenthesis
proc shuffle:SH { list } {
set n [llength $list]
for {(i=0; L is List)} { $i < $n } { incr i } {(
j = int(rand()*$n);
L($j) = (tmp = $L($i));
L($i) = $tmp;
)}
return $L
}This is becoming a lot more clear now ! But, of course, that implies some work in other parts of Tcl.
As you said, there is also a need for a more expressive syntax for list indexes. The signed integer is a very good idea. We would just write set a $L(-$i). I would also like to have a disjoint list of index like $L(1,4,7) to say list [lindex $L 1] [lindex $L 4] [lindex $L 7], a way to get a range : $L(0..-1) or even to get a set of disjoint ranges : $L(0..3,-2..end). The last would be the way to index in a nested list : set Vect = $Mat(0(0..2)) = $Mat(0(0,1,2)) = $Mat((0(0),0(1),0(2)). But that would be such a big work to get it !
So, let me go back to ontology. Another big work. let me give an example with the is operator. We can "compute" ontology. Ocaml language is doing it for instance. We can compose type with some operations.
[( num is {int + double}; # type definition (union of type)
vect is {num * num * num}; # type definition (array of 3 nums)
(v0 as vect) = (1, 2, 3);
(v1 as vect) = (4, 5, 6);
matrix3x3 is {vect * vect * vect}; # type definition (array of 3 vext)
(m as matrix3x3) = ($v0, $v1, (7, 8, 9));
a = $m(2,2)
)]; # returns : 8
[( unit is {("px", "mm","cm") of "string"};# enum "unit" definition
point is {(x : num) * (y : num) * (z : num) * (u : unit)}; # type definition with named fields
(P as point) = (10, 20, 30, "px"); # use the order of declaration.
# use the names of the fields :
(Q as point) = (
x : $P(x)*[px2cm],
y : $P(y)*[px2cm],
z : $P(z)*[px2cm],
u : "cm"
);
;)]NB : I introduce the syntax ( key : value, key : value ) to set a dict
[( (D as dict) = ($k0 : $v0, $k1 : $v1, $k2 : $v2) )] set key $D(:$v0); # $k0 set val $D($k0); # $v0
[( bool is {
("true", "false", "yes", "no") of "string"
+ (0, 1) of int
+ (0.0, 1.0) of float
};# standard Tcl enum bool definition
(b as bool) = "true";
# unary version of is : return the type of the value
is $b
)]But more operator are needed now, since there is type management to be done now. We need to configure translation from one type to another. For this, there could be an operator "to" . We also need an explicit cast directive. For this, there could be the ternary operator : $val from $typeInitial to $typeFinal
# 1. Complex number type definition
[( num is {int + double}; # type definition (union)
complex is {(real : num) * (img : num)}; # type "complex" definition (array of 2 num)
(string to complex) = {s {(
real : [lindex [::math::complexnumbers::fromString $s] 0],
img : [lindex [::math::complexnumbers::fromString $s] 1]
)}
}; # define a lambda to be applied (in expression parsing context)
(complex to string) = {
z {::math::complexnumbers::toString $z}
};# define a lambda to be applied (in command parsing context)
"*" prefix "cmplx" = "::math::complexnumbers::*";
"+" prefix "cmplx" = "::math::complexnumbers::+";
"|" prefix "cmplx" = "::math::complexnumbers::conj";
;)]
# define complex multiplication :
proc ::math::complexnumbers::* {c z} {(
(c, z) are complex;
(type = (is $c)) ? $c from $type to "complex"; # unary version of "is" operator returns the type of a value
(type = (is $z)) ? $z from $type to "complex";
real : $c(real)*$z(real) - $c(img)*z(img),
img : $c(real)*$z(img) + $c(img)*z(real)
)}
# ...etc
[( (a, b, c, d) are complex;
a = ("1+i" from "string" to "complex"); # convert from string
b = (3, 1); # direct assignement from list (in the order of the initial declaration)
c = ("real" : 0.5, "img" : 4); # direct assignement from dict
d = ($a {cmplx}+ $b) {cmplx}* ( {cmplx}| $c )
)]synthetic table to work with typed values in expressions :
| operator | type | context | left operand | right operand | third operand | meaning |
|---|---|---|---|---|---|---|
| .. is .. | binary | expression | variable name | type name | assign a type to a variable, return the type | |
| .. are .. | binary | expression | list of var names | type name | assign a type to a list of variable, return the type | |
| is.. | unary | expression | variable name | get the type of a variable | ||
| .. as .. | binary | expression | variable name | type name | assign a type to a variable, return the variable name | |
| .. of .. | binary | type definition | list of values | type name | restrict the type to a list of value | |
| .. : .. | binary | type definition | field name | type name | define the type of a field name, return the type | |
| .. to .. | binary | expression | type name | type name | define a casting directive between two types, return a generated cast name | |
| from .. to .. | ternary | expression | value | type name | type name | apply a casting directive |
| .. prefix .. | binary | expression | operator | prefix name | create a custom prefix operator, return a prefixed operator name |
AMB - 2026-04-23 17:11:47 ND-list data structure
Before getting to an operator for ranged indexing, I want to introduce a concept I have been developing for some time: the "ND-list" data structure. I have a pure-Tcl prototype of it already developed: the package ndlist. It introduces the concept of "Everything is an ND-list" to Tcl, which is already true: everything in Tcl is an NDlist, for some rank "N". The definition of an ND-list is as follows:
If this is implemented in the Tcl core, it would add the metadata of "rank" to each Tcl value. By default, new Tcl values would have a rank of zero, because all strings are 0D-lists, and all valid Tcl lists would have a rank of at least 1. Higher rank values would require the addition of an ensemble of commands for accessing, modifying, and manipulating ND-lists. I have developed a lot of prototypes of these commands in my ndlist package.
The way I imagine this working is that when a Tcl value is accessed by an ND-list command that expects a certain rank, it would first check if the rank stored with the value is greater than or equal to the expected rank. If so, the command continues and does whatever access/modification/manipulation it does, and leaves the rank unchanged (unless if the command explicitly modifies the rank of the list). However, if the rank that is stored is less than the expected rank, it will check to see if it is a valid ND-list for the expected rank. If so, it will update the rank of the value and continue. If not, it will throw an error, like how Tcl list commands throw an error if the value is not a valid Tcl list.
If you access an ND-list with the indexing features of ndlist, the rank of the indexed range is preserved. So, nget {{hello world} foobar} 0 returns {hello world} not hello world. If you want to index into an ND-list, you can either use the lindex command, or use floating point numbers for the index value. So, nget {{hello world} foobar} 0.0 would return hello world.
This concept could be used as a starting point for advanced indexing features in Tcl expressions. Then, a different operator, such as @@, can be used to indicate ND-list indexing within Tcl expressions.
AMB - 2026-04-27 20:30:18
Hi FM, I definitely prefer the "as" operator to the braced prefix for denoting types. It is a lot more readable. However, I am still not convinced that it is needed.
I understand the benefit of strongly typed values. It prevents something from being accessed/modified in a way that was not intended. But in Tcl, everything is a string, or at least can be shimmered between datatypes where possible. Perhaps the way that types are introduced in Tcl is by just fixing the type of a variable. This should be introduced at the command level before adding operators or otherwise changing the Tcl parser.
For example, the command "type" could be introduced, that either queries or sets the type of a variable. Setting the type to "{}" would "free" the variable to shimmer at will.
type varName ?type?
Then, variables that have a fixed type would not be able to be modified in a way that changes their type.
Regardless of the way it is implemented, I think it needs to be handled at the command level in Tcl before being implemented as an operator. A pure Tcl prototype could be developed with Tcl variable traces as a proof of concept.
Going back to the @ operator, I have a modification to my proposal: I think that the default behavior should be "ndlist" indexing. My prototype ndlist provides a way to access nested Tcl lists in a way that allows for things like multiple indices at one level, but it also allows for indexing in the style of "lindex"
For example, with my ndlist prototype, if you wanted to get a sublist of a list, such as elements from indices 1 and 2, you would just type nget $x {1 2}.
package require ndlist
namespace import ndlist::*
set x {{foo bar} {hello world} {goodbye moon}}
# puts [list [lindex $x 1] [lindex $x 2]]
puts [nget $x {1 2}]; # {hello world} {goodbye moon}However, you can still do single indexing using floating point indices instead of integers (integer indices preserve the rank of the ndlist).
puts [nget $x 1]; # {hello world}
puts [nget $x 1.]; # hello world
puts [nget $x {1 2} -1]; # world moonSo, my ndlist index notation allows for lists of indices to be entered at each level, where an integer is parsed as a list of length 1, preserving the rank of the resulting indexed value.
In order to implement this indexing scheme with an operator in a Tcl expression, you would need to be able to pass a singleton list that contains a list of indices. This is the use case that I had in mind but had a difficult time expressing for the trailing comma to denote a singleton list. However, upon further reflection, based on our conversation, I think the better way to implement this is to create a unary version of the comma operator that denotes a singleton list.
So, if ndlist indexing was implemented as the default behavior for the @ operator, you would replicate the example above with the following:
set x {{foo bar} {hello world} {goodbye moon}}
puts [nget $x {1 2}]
puts [($x @ (,(1,2)))]; # {hello world} {goodbye moon}
puts [($x @ 1)]; # {hello world}
puts [($x @ 1.0)]; # hello world
puts [($x @ ((1,2),-1))]; # world moonThis would handle all forms of list indexing, both simple and advanced.
For other data structures - I don't really see the added value in having the @ operator indicate anything other than list (or ndlist) indexing. Indices of lists and ndlists are with integer values, so it makes sense to have an operator for it. You can then easily perform arithmetic on indices. Dictionary indices, on the other hand, are usually string values, so there isn't much utility in exposing it at the expression level.
Regardless, if we are adding an operator for dictionary access, it would be much clearer if there was a separate operator for it. For example, the operator @: could denote dictionary access. Then you could write the following:
# set mydict [dict create {foo bar {hello world} {goodbye moon}}]
puts [(
mydict = ("foo", "bar", "hello world", "goodbye moon");
$mydict @: (,"hello world")
)]; # goodbye moonNote that I did not use the ":" notation for key-value dictionary access. I do not have an issue with the notation specifically, but I hesitate to give approval of it as it is purely cosmetic. I was thinking that the colon operator could be used for ranged index notation instead.
Note also the use of the unary "," operator to denote a singleton list. This is another use-case for it - dictionary keys can have spaces in them, so being able to distinguish between a single key that has spaces and multiple keys is critical.
As an aside, with all of this in mind, I think it has become pretty clear that bareword variable access in Tcl expressions should not be implemented. We discussed the possibility of this before, but barewords should be reserved for operators and for variable names before the assignment operator. Otherwise, adding new operators, especially ones composed of standard ASCII characters, would be impossible without breaking backwards compatibility. There is a lot of room for improvement in the Tcl expression engine, and adding bareword variable substitution would cripple future development.
AMB - 2026-04-28 13:46:00
Thinking about it more, I don't like the look of the unary comma operator. Instead, I think that a simple mathfunc should be added for generating a list. Because of how basic of an operation it is, I propose that it just be the letter "l".
proc ::tcl::mathfunc::l {args} {return $args}
expr {l("hello world")}; # {hello world}
expr {l(1,2,3)}; # 1 2 3For more than one argument, the "l" can be dropped, as it has commas which denote a list per FM's prototype.
This change is easy to make, it could even just be added to the init.tcl file.
So, the example I had above with my idea of how the @ operator does ndlist indexing would be rewritten as follows:
set x {{foo bar} {hello world} {goodbye moon}}
# With the ndlist package
puts [nget $x {1 2}]; # {hello world} {goodbye moon}
# With proposed notation
puts [($x @ l((1,2)))]; # {hello world} {goodbye moon}
puts [($x @ 1)]; # {hello world}
puts [($x @ 1.0)]; # hello world
puts [($x @ ((1,2),-1))]; # world moonFM : here are a lot of very creative ideas !
Sadly, index arithmetic isn't perfectly analog to number arithemetic.
While these analogies are very interesting, I'm afraid little coner cases like these will make it impracticable.
Index calculation is tied to expr calculation, but is not identical. It's interesting to specify it, but there is a need for another wiki page for this, for instance "A better way to do indexation".
Now, to get back to expr shorthand : nobody in the core team seems to be interested by this proposal [( ... )] or {( ... )}. Worse, I recieved a lot of negative opinion about it. It's like Tcl core team doesn't want to make a move, they seem to satisfy themselves with the actual situation. Let's wait a little, but, for the moment, nobody in the core want to sponsor this proposal. So it's not possible to publish a TIP. So it can be neither tested, neither discuted in the Tcl core team.
AMB - 2026-04-29 15:09:49
Regarding my index notation for ndlists, yes, -1 + 1 does give you 0. This is the expected behavior. My proposed index notation does NOT allow for expanding a list with "end+1" notation. That would have to be done with concatenation or appending a list.
And regarding the floating point indices, this is just a misunderstanding. The notation ($x @ 1.0) would not represent lindex $x 1 0. It would just represent lindex $x 1. To get lindex $x 1 0, you would type ($x @ (1.0,0.0)). Otherwise, the notation ($x @ (1,0)) would be equivalent to list [list [lindex $x 1 0] ]. A floating point index at one level just indicates that the indexer "slices" the ndlist at that level. This is more critical when you start getting into ranged indices and multiple indices at a given level. If you wanted to slice an ndlist at a level, you would just multiply the index by "1.0" to slice it, basically. If you want a deep dive, look into the functions in my ndlist package. Floating point indices trigger the "Single" index access switch, while integer indices are interpreted as "List" index access.
Regardless, you are correct, the index notation discussion is a discussion for another thread.
You mentioned that the Tcl core team doesn't seem to have interest in the [(...)] notation. Have they given you any feedback as to why that is the case? The only thing I can think of is the objection that I have already brought up - I think that the notation isn't generalized enough.
I really think that in order for this notation to be adopted, it needs to be implemented in a single way that covers all the edge cases. I have proposed the following before:
If open parentheses are encountered FIRST in a command, it signals an expression environment, and then it searches for the close-parentheses. This would cover both [(...)] and {(...)} cases (and the "(...)" case, and others).
So, the following should all be valid:
(x = 10.0); # bare parentheses on a new line
(mylist = ($x,1)) ; # bare with whitespace
lappend mylist [($x + 2)]; # within brackets
lappend mylist [ ($x/5)]; # brackets with whitespace
lmap val $mylist {($val + 6)}; # within braces
lmap val $mylist {
($val * 5)
}; # braces with whitespace
set myexpr {$val + 2}
lmap val $mylist "($myexpr)"; # within quotes
lmap val $mylist " ($myexpr) "; # quotes with spacesThe point is, if it is defined at most primative level of the parser, it covers all the use cases you have implemented individually, and more. Instead of covering all these edge cases, just have it so that when the parser is parsing a command or script, and the first non-whitespace character it sees is an open parentheses, it interprets it as an expression!
AMB - 2026-04-29 16:06:21
Another thought about the TIP for expression shorthand --
Perhaps the TIP should ONLY be about the (...) notation. While I absolutely love the other improvements that we have discussed, for buy-in from the core team it may be best to just focus on the most critical aspect for now, which is the shorthand.
Once that is hopefully adopted and implemented in the core, we can follow up with the other stuff like variable assignment, multiple expressions, native list notation (and the list expansion operator), and eventually element-wise operations and advanced indexing, among other wishlist items.
I believe that once the shorthand notation is properly implemented and adopted, it will open up the floodgates for expression notation improvements from the rest of the community.