Expanding the Expansion Prefix

Difference between version 75 and 76 - Previous - Next
[AMB]: The expansion prefix {*}, or [BraceStarBrace], was a great addition to Tcl. It eliminated the need to call "eval" a lot, making code cleaner and more secure. Personally, I can't imagine doing Tcl without the expansion prefix. Similarly, https://core.tcl-lang.org/tips/doc/trunk/tip/401.md%|%TIP #401%|% proposed that {#} be implemented as a prefix for comments within commands. 

Why stop there? Why not introduce a general prefix syntax for Tcl that extends the existing functionality of {*}, includes the proposed functionality of {#}, and opens things up for customization, in typical Tcl fashion? Based on some conversations here on the wiki with [FM] and others, I came up with the following:

EDIT: When I posted this originally, I did not realize that the Tcl [prefix] command existed.

***Syntax***

A new command called "prefix" would be added, with subcommands "add" and "remove" (at a minimum).

The syntax of this proposed command is as follows:

======
prefix add name commandPrefix
prefix remove name
======

where "commandPrefix" behaves like the Tcl "trace" command and executes in the level that it was called.

The word that follows the registered prefix would take a single argument, like how the {*} prefix currently works, and the returned list would be expanded.

Using this syntax, the following code could then re-implement the existing expansion prefix {*}, as well as the proposed comment prefix {#}:

======
prefix add {*} {apply {{word} {return $word}}}
prefix add {#} {apply {{word} {return}}}
======

***Examples: ***

A custom prefix {@} as a shortcut to expand a Tcl array.
======
# Create the prefix for expanding the values of an array
prefix add {@} {array get}

# proc that prints the args in option: value format
proc printOptions {args} {
    foreach {option value} $args {
        puts "$option: $value"
    }
}
# Define options in an array
set options(-foo) bar
set options(-hello) world
printOptions {@}options; # prints "-foo: bar\n-hello: world"
printOptions {*}[array get options]; # equivalent expression.
======

Another one I could see being popular: {c} as a shortcut to split a string by commas.
======
prefix add {c} {apply {{word} {split $word ,}}}
list {c}0,1,2,3; # returns 0 1 2 3
list {*}[split {0,1,2,3} ,]; # equivalent expression
======

I think this proposed feature has a lot of potential to "expand" the capabilities and expressiveness of Tcl.

What do you all think?

[FM] I think I would like it if the prefix can take parameters (enclosed into parenthesis, separated by commas). Also, I'd like to be able to configure it so that the result of the prefix operation should be expand by the parser. For Instance :
======
# parameters of word prefix, special -expand boolean parameter
# Null prefix with no expansion : return one argument as list
interp prefix $interp {} -param separator -expand 0 {apply {{{separator {}} word} {split $word $separator}}
foreach char {}$String {} ;              # null prefix : by default, gives the list of chars.
foreach char {(",")}$String {} ;         # parameter "," : split by commas
foreach char {(" ")}$String {} ;         # parameter " " : split by spaces
foreach char {(";"," ",",")}$String {} ; # parameters ";" and " " and "," : split by semi-colons, spaces and commas

# "*" prefix : return as many arguments as the list has elements
interp prefix $interp "*" -param separator -expand 1 {apply {{{separator { }} word} {split $word $separator}}
list {*}{1 2 3 4};       # by default : return 1 2 3 4
list {*(" ")}{1 2 3 4} ; # return 1 2 3 4
list {*(",")}1,2,3,4 ;   # return 1 2 3 4
list {*(";")}1;2;3;4 ;   # return 1 2 3 4
list {*(.)}1.2.3.4 ;     # return 1 2 3 4
======
I'd like also these prefix can be applied to another one. What if a prefix is applied to prefix whose result is to be expanded ? Let's imagine it will be applied to each of arguments which resulted from the expansion.
======
# Composition of Word prefix
list {{*}()}$S ;# equivalent to list {*}[split $S ""]
list {{*}(",")}$S ;# equivalent to list {*}[split $S ","]

# Apply « expr » to each of the expanded arguments that were produced previously by splitting the word by ";" and return a list
set exprList {{{=}*}(";")}{$x+1; $y+2; $z+3}; 

# or (shorthand) :
interp prefix $interp {;=} {apply {{word} {
      return [uplevel [list {{{=}*}(";")}$word]]
   }}
}
set exprList {;=}{$x+1; $y+2; $z+3}

# Double expansion        
proc ARGS args {return args}
ARGS {{*}*}{{1 2 3} {4 5 6} {7 8 9}}; # return 1 2 3 4 5 6 7 8 9

======


----
[KSA]: In general an interesting idea. However I wonder which use-cases there are that require it. For example things that can't be done by simply writing a `proc` (or things that are at least troublesome using a `proc`). It's obvious that a `proc` is of no help for what `{*}` does and the same is true for `{#}` as well. But apart from these two?

Okay, after some thinking, this might be another use-case: Would it be possible to simplify array arguments by introducing references (e.g. using a `{&}` prefix)? Example:
======
proc print_array {v} {
   foreach key [array names v] {
      puts "$key: $v($key)"
   }
}
array set x {a 0 b 1 c 2}
print_array {&}$x
======
But on the other hand the current solution is not much more complex:
======
proc print_array {v_} {
   upvar $v_ v
   foreach key [array names v] {
      puts "$key: $v($key)"
   }
}
array set x {a 0 b 1 c 2}
print_array x
======
----
[FM] I would prefer a more explicit {array} prefix.

Let's say we have a general rule : Every prefix that the main parser doesn't recognize, is set as an « Object type ».
If we had an « array » Tcl_ObjType, the we could write :
======
set Arr {array}[list key0 val0 key1 val1 key2 val2]
======
Then, the parser would pass a Tcl_ObjType « array » to the set command, which would act accordingly, eg : calling the fonction « setArrayFromAny » that is strore in this Tcl_ObjType.

Maybe the [https://github.com/cyanogilvie/type%|%type%|%] package of Cyan Ogilvie could be usefull here : We could define new Tcl_ObjType on the fly, then juste use their added possibilities. To take an example of this package :
======
define XML {
    create  {apply {val {
        dom parse $val
    }}}
    string  {apply {intrep {
        $intrep asXML
    }}}
    dup     {apply {intrep {
        dom parse [$intrep asXML]
    }}}
    free    {apply {intrep {
        $intrep delete
    }}}
}
set Data {XML}{<foo><bar>Bar text</bar><baz>Baz text</baz></foo>}

with xml $Data {
    [$xml documentElement] setAttribute something changed
}
======
This said, as an array is not a Tcl_Object, things would be more complicated in this case.
----
[AMB] The implementation that I proposed would not allow for the sort of array referencing you show. The proposed prefix notation works exactly like the existing {*} notation, in that it returns multiple arguments, but with similar functionality to a single-argument proc. This would be consistent with the behavior of {*} and allow for the proposed behavior of {#} for comments. 

With my proposed implementation, your example would return an error, because the interpreter would still parse the word after the prefix, and $x would return an error because x is an array. 

I think passing variables as references in procs is a different problem, best handled by a modification to the syntax of proc itself.

Edit: upon reflection, you could introduce something to call within the body of the proc as a shorthand for “upvar”


Here is an example of how I could see it be implemented. I don’t know if I like it, again I think that passing variables by reference should be a change to the syntax of proc itself. 
======
prefix add {c} {apply {{csv} {split $csv ,}}}
prefix add {&} {apply {{vars} {
    set command [list upvar 1] 
    foreach var [list {c}$vars] {
        lappend command $var $var
    }
    return $command
}}}

proc foo {} {{&}a,b,c
    puts [list $a $b $c]
}

set a 1
set b 2
set c 3
foo; # prints 1 2 3
======


More examples:

Double expansion. Kinda goofy lol. 

======
prefix add {**} {apply {{matrix} {concat {*}$matrix}}}
set matrix {{1 2 3} {4 5 6}}
foreach element [list {**}$matrix] {
    puts $element
}
# prints 1 2 3 4 5 6
======

Not all prefix use cases return multiple arguments. Here’s another example: this one returns one value: a file path with spoofed tilde substitution. Note that the returned value is a one-element list. 

======
prefix add {~} {apply {{path} {
    list [file join [file home] $path]
}
source {~}/myfiles/hello.tcl
======

Another single-arg use case that people might use is shorthand for expr. 
Personally, I think that an alias of = for expr is good enough, but this would be a possibility with the proposed “prefix” command. 

======
prefix add {=} {apply {{expr} {
    list [uplevel 1 [list expr $expr]]
}}}
set x 5
set y {=}{$x * 4}; # 20
======

Another idea is to convert key=value values into a dictionary and then pass the values on as individual arguments. 

======
prefix add {keys} {apply {{input} {
    set result {}
    foreach entry [lmap entry [split $input ,] {
        lassign [split $entry =] option value
        dict set result -$option $value
    }
    return $result
}}}

proc printOptions {args} {
foreach {option value} $args {
    puts “$option: $value
}
}
printOptions {keys}{foo=bar,hello=world}
# prints “-foo: bar” and “-hello: world”
======

Essentially, the majority of potential use cases I see is where you are already using {*}, as well as a few convenience cases for one-argument output.

Edit: you could also use it as a sort of annotation for specific types of values. 

For example:
======
prefix add {f} ::tcl::mathfunc::double
set x {f}10; # 10.0
======

[MG] If I've understood the intent correctly, for `{#}` to be for inline commenting in the middle of a command, wouldn't it result in an additional null argument? If you had, for instance
======
foo $bar {#}"this is a comment" $baz
======
You'd end up running $foo with 3 arguments, the second being an empty string, wouldn't you? I don't see any way around this is if `{#}` is implemented at the Tcl-level, and it would be a proper parser change if it's implemented at the C level to have an argument parsed and completely ignored where it starts with `{#}`

[CGM] Yes the `prefix` command itself has to be implemented in C and integrated with parsing, as {*} is now. But then the code it runs for each case can then be written in Tcl.

<[FM]> The way I see it : some prefix should be recognized by the parser itself, beeing general syntax rules of Tcl itself, like the prefix {*}, {=} or {#}, in a way that the parser can operate with them directly, eventually in modifying the number of arguments of the command line, like it is for {*} prefix and like it should be for the {#} prefix.

But some other prefix, unknown to the parser, should just be pass as « metadata » tied to the Tcl_Object holding the argument. This way, the C fonction could be configure to detect a specific prefix (by the metadata of its arguments) and act in consequences.

For instance, let's say we have a {lst} prefix, unknown to the parser, and let's write this command in console :
======
set {lst}L A B C D
======
the Tcl_SetObjCmd will recieve a 5 arguments objv :
   * the first one is a tcl object with string representation « L », tied with a metadata {lst}
   * the last one are simple tcl objects
If Tcl_SetObjCommand is able to detect the metadata of the first argument, and has been made aware that this {lst} prefix denotes a list, no error sould be thrown, the SetObjCmd can create immediately a var « L » that is a list whose content has to be all the last arguments of objv.

Similary, lets say we have an {args}prefix which is unknown by the generic Tcl parser, and let's write this command in the console
======
proc foo {args}{
   {#}{my improved argument specification language here}
   {&}myUpvar
} {
   # body of the proc
   return $myUpvar
}
======
The parser will simply pass to the proc command a two objc lentgth objv[[]], first element beeing the arg Tcl_Object, tied with the metadata object {args}, and the second one beeing the body Tcl_Object. If the proc command is made able to detect this specific {args}prefix metadata, it can use a new specific argument parser that a TIP has describe.</[FM]>
----
[CGM] Wow! I had been thinking of the same idea but had not found the time to write it up.  I particularly like {=} as a compact way to invoke [expr], which obsoletes my own https://core.tcl-lang.org/tips/doc/trunk/tip/676.md%|%TIP 676%|% .
----
<[FM]> Let's continue to explore the « set modulation » possibilities... 
Let's generalize the ''()'' to denote part of any collection type : arrays, list, dict, tree, matrix, graph
Let's explore the possibilities that are offered to a list variable.
Imagine we denote a continuous range with ''->'', and separate discrete elements by a pipe ''|''
======
set {lst}L a b c d e f g
set i 3
set result0 {lst}$L(0->{=}$i-1|{=}$i+1->end) ; # result : a b c e f g
set result1 [list {*}[lrange $L 0 [expr {$i-1}]] {*}[lrange $L [expr {$i+1}] end]] ; # result : a b c e f g
======
Moreover, if expr is made aware of metadata, it can also modulate its behaviour, knowing the « type » of a variable
======
# let's denote a 3x3 matrix by a {m(3x3)} prefix
proc tcl::mathop::* {left right} {
    set TypeLeft [info metadata name $left]
    set TypeRight [info metadata name $right]
    switch $TypeLeft {
       m {
          # matrix type data on the left : must be the same on the right
          if {$TypeRight eq "m"} {
              # must check if the two matrix dimensions are well suited
              set i [lindex [split [lindex [info metadata parameters $left] 0] "x"] 0]
              set j {lst}[split {lst}[info metadata parameters $right](0) "x"](1)
              if {$i == $j} {
                   tcl::mathfunc::matrix::* $left $right
              } else {
                   puts stderror "dimension incompatibility between matrix"
              }
          } else {
              puts stderror "type incompatibility between arguments"
          }
       } default {
            tcl::mathfunc::number::* $left $right
       }
    }
}
set {m(3x3)}Mat1 {
   1.0 2.0 3.0
   2.0 3.1 3.8 
   0.5 1.3 5.7
}
set {m(3x3)}Mat2 {
   1.0 2.0 3.0
   2.0 3.1 3.8 
   0.5 1.3 5.7
}
set Product {=}{
   $Mat1 * $Mat2
}
======
Conclusion : 
   * Prefix could be seen as metadata.
   * This metadata value could be retrieve by a command : ''info metadata name /objectName/''
   * Metadata could have parameters
   * The list of parameters could be retrieve by a command : ''info metadata parameters /objectName/''
   * The rule could be to have a comma separated list of parameters enclosed in parenthesis : ''{meta(param1,param2,...etc)}Word''
</[FM]>

[AMB]: If the prefix notation is used for defining types in the "set" command, the syntax of the "set" command should be modified to allow for this. Here is how I see it working:

======
set varName ?-type type? ?value?
======

The optional "type" argument would assert the input is that type, and then attach metadata to it which would reinforce the type whenever there is access to the variable, unless overwritten by calling set with a -type specified again. This would reduce unwanted shimmering in scripts. 
The default type would be {}, and the type of a variable could be accessed through "info type varName".

Expanding upon this, a new command called "typedexpr" or something could be added that returns "-type type value", determining the type of the resulting expression based on the types of the variables accessed. This could potentially allow for vector math, which is a feature that has been widely requested.

Then, prefixes could be defined to annotate your code as shown:

======
prefix add {num} {apply {{varName} {
    list $varName -type double
}}}
prefix add {=} {typedexpr}
======

======
set {num}x 10; # equivalent to "set x -type double 10"
set {num}y 4
set z {=}{$x/$y}; # expands to -type double 2.5
puts [info type z]; # double
======

I reiterate: I do not think that the {prefix} notation should have any significant meaning besides returning multiple arguments. It can be combined with other features to appear to have special meaning, but ultimately it should just return a list that is expanded by the parser.

Edit: Another reason for this proposed behavior of the prefix notation, besides being consistent with the existing behavior of {*}, is that it would not add any rule to the [Dodekalogue]. It would simply modify the rule about argument expansion.

[FM] : Only the {*} prefix is expanding the command line arguments. {#} prefix will contract it from one (it will cancel its own). {=} will let it of constant size, just changing the way the word is interpreted. 

The prefix syntax will not be reduced any more to the expanding operator case. It's still one rule, '''a word prefixed by {prefix} has a special meaning''', but this rule will become like a tree (composed of 2 main chapters) :

   * rule : a word prefixed by {prefix} 
   ** {prefix} recognized by Tcl parser which allows special action of it :
   *** Expansion argument prefix {*}
   *** comment argument prefix {#}
   *** math mode argument prefix {=}
   ** {prefix} unknown by Tcl parser which is simply tied as metadata to the argument objv of the command. This is the reponsability of the command to use or not this metadata and to do whatever it wants with it. Some prefix are predefined for some commands (refer to their command manual to know their effect).
   *** predefined prefix
   **** Prefixes recognized by « '''set''' » (refer to this command manual)
   ***** prefix on 2nd argument : 
   ****** {list} : take this var as a list. 
   ****** {dict} : take this var as a dict.
   ****** {string} : take this var as a string.
   ***** prefix on 3rd,..., nth argument : 
   ****** {list} : take this value as a list. 
   ****** {dict} : take this value as a dict.
   ****** {string} : take this value as a string.
   **** Prefixes recognized by « '''proc''' » (refer to this command manual)
   ***** prefix on 2nd argument :
   ****** {namedargs} : parse proc arguments as named argument
   ****** {args} : use the new argument parser, allowing to mix named et positional arguments 
   ****** {xml} : parse argument as a xml string (access with tdom)
   ****** {c} : parse arguments as C language arguments
   ***** prefix on 3rd argument :
   ****** {c} : compile body as C code
   ****** {asm} : compile body as asm 
   **** ...etc
----
[KSA] Just a remark to the interesting example given by [FM]: Unfortunately it's currently not possible to override/replace math operators. It's possible to redefine `::tcl::mathop::*` procs but Tcl does not care about this and will not call this when evaluating `expr`. There's a small remark in the docs that confirms this: https://www.tcl-lang.org/man/tcl/TclCmd/mathop.htm%|%::tcl::mathop%|%. I would be delighted if this could be changed in future versions of Tcl. In fact this was a show-stopper for my attempts to add list/vector support to the `expr` command.

Maybe I'm a bit blind here, but I don't see the benefit of replacing `expr` by some other notation yet. If users are not satisfied with `expr` they could do the following:
======
proc = {args} {
   return [expr $args]
}
set x [= 42 * 8 + 1]
puts $x
======
Or even more drastic:
======
proc unknown {args} {
   return [expr $args]
}
set x [1+1]
puts $x
======
This is straight forward only using known Tcl rules, no magic needed.

Adding metadata could be interesting but may require some careful thinking: For example what happens if metadata declares that a variable `$x` contains a 3x3 matrix but value of `$x` is later changed to say "Hello world"? Does altering the value of a variable remove its metadata or is it assigned as long as variable exists (even if the value is no longer what the metadata suggests)? Can a variable have multiple metadata "tags" assigned? This might be important when passing variables between packages that assign "their" metadata "tags". One package should not remove the metadata the other package relies on and vice versa. What if two variables with metadata assigned are concatenated? etc.

[FM] 
We can consider the difference :
======
> set {matrix(3,3)}Id {{1 0 0} {0 1 0} {0 0 1}}
{{1 0 0} {0 1 0} {0 0 1}}
> set {matrix(4,4)}M $Id
"Type mismatch between matrix(4,4) matrix(3,3) : unable to set M"
> set M {matrix(4,4)}$Id
"warning : implicit conversion form matrix(3,3) to matrix(4,4)"
> set Id "Hello word", # error
"unable to interpret 'Hello world' as a matrix(3,3)"
> unset Id
> set Id {matrix(3,3)}{{1 0 0} {0 1 0} {0 0 1}}
{{1 0 0} {0 1 0} {0 0 1}}
> set Id "Hello word"
Hello World

======
If it's the varname that is tagged, the var should be typed, and no shimering should be allowed any more.

But if it's the var value that is tagged, it's a normal var, that allows shimering as usual.

So we can choose : Will my var need a fixed type ? Should I allow it to Shimmer or not ?

The question of concatenation :  it is far more complex. It depends on how the set command react to the metadata.
======
> set {list}L 1 2 3
> set {dict}D k1 v1 k2 v2
> set {string}S "hello world"
# A simple concatenation just concatenates the values (everything will shimmer)
> set A [list $L $D $S]
"{1 2 3} {k1 v1 k2 v2} {hello world}"
# A more sophisticate solution, that won't shimmer : We need to keep trace of each component.
> set {struct}myStruct \
    {{list}myList}$L \
    {{dict}myDict}$D \
    {{string}myString}$S
"mylist {1 2 3} myDict {k0 v0 k1 v1 k2 v2} myString {hello world}"
> set {myList}myStruct(1)
"2"
> set {myDict}myStruct(k2) 
"v2"
> set {myString}myStruct(0->4)
"hello"
======
I Need to think more about it.
----
'''[AMB] - 2025-06-12 11:55:31'''

I’ll be honest, I really don’t like any interpretation of a prefix other than one that expands arguments. I was simply suggesting that you could define custom prefixes that do preprocessing of the input that comes after the prefix, as defined by commandPrefix. Again, this would be compatible with the existing {*} prefix, and the proposed comment prefix {#}. It wouldn’t radically alter the language. 

I want to be able to define a custom prefix within a Tcl session with a new command “prefix”, in a way that is completely compatible with the existing {*} operator. That is all.

Oh, and in regard to shorthand for expr, I agree that an alias of "=", and/or importing the math operators and functions directly for prefix notation is sufficient, but a custom prefix would be another option.

Edit: Last weekend I did a deep dive in how the parser works, and specifically how it interprets the “expansion prefix”. From my understanding, it should be easy to have it look up registered prefixes like it looks up commands and then expand the word as usual.

Edit edit: Upon further reflection, I realize that as it stands, the {*} only has meaning within a Tcl command, and is not actually valid syntax within lists or dictionaries. So I see the potential to utilize the {prefix}word syntax for rigid type annotation, even within lists.

[FM] I know you dyslike it. Personnaly, I don't want to have a preference « a priori ». A lots of talks where skeptical about the expand operator {*} syntax. Nowadays, nobody is complaining anymore about it, everybody is just using it. So, I just want to see the possibilities the concept can offer.

But your a quite right : expand {*} operator is not implemented into list. Let's type :
======
set L {1 {*}{2 3 4} 5}
lindex $L
"list element in braces followed by "{2" instead of space"
======
L is not a valid list : it will remains a string forever. The complain come from FindElement in tclUtil.c, which is waiting for a cannonical list representation. What if the cannonical List representation was allowed to accept, and recognized, the expansion operator ? We would have instead :
======
> set L {1 {*}{2 3 4} 5}
> lindex $L
"1 2 3 4 5"
======
I don't see any use case of this construct. 

But there is one I'm missing sometimes, in the expression parsing context. Let's imagine you have a list of values, and you want to calculate the min, the max, the mean, or apply any statistical function to the values of this list :
======
> set L [list 1 2 3 4 5]
> set max [expr {max({*}$L)}]
"missing operator at _@_
in expression max({*}_@_$L"
> set max [expr {max([join $L ,])}]
"expected floating-point number but got 1,2,3,4,5"
======
I even can't brace my expression... I must write this, which is a weird and not really explicit :
======
> set max [expr max([join $L ,])]
"5"
======
In the expr context, the expansion operateur {*} should separate the elements of the list with commas. This way, with an {=} expression operator, we could just write :
======
> set max {=}max({*}$L)
======
Which is a lot more clear IMO
----
'''[AMB] - 2025-06-16 15:22:15'''

Well, upon thinking about it more, I don't mind there being other special meanings to {prefix}word beyond the expansion prefix. Also, I did not know of the existence of the [prefix] command in Tcl, so my proposed syntax would conflict with an existing command. I also realize that the syntax error of characters after a brace (or quote, for that matter) is an opportunity to expand the language, and thus a solution like what I proposed could potentially permanently stunt the future of the language.

Regarding the issue with the math "max" and "min" commands, I agree, it is annoying that we have comma-delimited arguments in expr and space delimited everywhere else. There are a few ways to get around it though:

1. Expose the Tcl math functions
======
namespace path ::tcl::mathfunc
set L [list 1 2 3 4 5]
set max [max {*}$L]; # or "set max [expr {[max {*}$L]}]"
======

2. Create your own max mathfunc that takes a list rather than multiple arguments
======
proc ::tcl::mathfunc::maxl {list} {
    ::tcl::mathfunc::max {*}$list
}
set L [list 1 2 3 4 5]
set max [expr {maxl($L)}]
======

Personally, I like to expose the math functions (and operators).

[FM] : To resolve the « max » fonction, import tcl::mathfunc this is the obvious solution. Just hope there won't be any conflict with preexisting names. On a little script it's, OK. But what if there is thousands of lines written by a big team before ?
======
namespace path ::tcl::mathfunc
proc max {args} {min {*}$args}
max 1 2 3
"1"
======
So, this trick can lead to some side effects. Thinking about it : If there is a namespace for mathfunc, is it not to avoid conflicts in names also ?

Moreover, this trick doesn't solve all the other blocking Tcl is suffering : Ok, it's good to have a simple syntax. But, this simplicity imply verbosity, which, on a big programm, introduce another kind of complexity. 

There is more fundamentals blocking also : Improve the way procs handle argument ? not possible, need a new proc. Simplify the way to compute numbers ? Not possible, need a new expr... Graph ? Trees ? Structures ? Do not speak about it... Just play with the structure of the string... And try to avoid corner case.

Word prefixes are the only chance to increase the power of the language.

Imagine, we had a {obj} prefix
======
# Let's define the Class
set {obj}Point {
     {#}{the {$} prefix denotes a property}
     {$}x 
     {$}y
     {#}{the {!} prefix denotes a method}
     {!}distance2orig {
          {{obj}Point}P
     } {
          return {=}{sqrt( 
                ({$}P(x) - $x)**2 
                + ({$}P(y) - $y)**2
          )}
     }
}
# Let's create the instance and initiate its properties
set O {obj}$Point x 50 y 50
set P {obj}$Point x 100 y 200
# Check a property
set X [{$}$P x]
# Call it's method.
set Distance [{!}$P distance2orig $O
======
---


[KSA]: I've published a testing project of mine that adds vector/list support to the expr math functions: 
https://github.com/ksaalfeld/exprplusplus%|%expr++%|%. Beside vector/list support it also adds some new expr math functions I considered missing or useful. It should replace the existing math functions to support lists/vectors without creating incompatibilities (hopefully). And it also "fixes" `max()` and `min()` math functions:
======tcl
set x {1 5 3 4 2}
puts [expr {max($x)}]
puts [expr {max(1,5,3,4,2)}]
======
The only limitation (for now) is that math operators can't be replaced. So, it wasn't possible to extend `+` or `*` operator to support lists/vectors. Instead new functions `add()` and `mul()` were added.
----
'''[AMB] - 2025-06-20 14:42:01'''

I took a look at your expr++ project. Looks great!!
----
'''[AMB] - 2025-07-02 20:26:23'''

What if the {=} prefix didn't just call "expr", but rather processed arguments in the same fashion as a math function in an expression, and returned multiple arguments like {*}? 

Here's what I am referring to:

Let's say you want to create a list with multiple arguments, some of which require calling the "expr" command.
======
set x 3.0
set y 4.0
set mylist [list $x $y [expr {hypot($x,$y)}] [expr {$x + $y}] [expr {$x*$y}]]
======

If there was instead a prefix in Tcl, say {=}, that allows you to do this instead:
======
set x 3.0
set y 4.0
set mylist [list {=}{$x, $y, hypot($x,$y), $x+$y, $x*$y}]
======

Another example, taken from [math::linearalgebra], could be re-written to be a lot more readable:

Original:
======
# crossproduct --
#     Return the "cross product" of two 3D vectors
# Arguments:
#     vect1      First vector
#     vect2      Second vector
# Result:
#     Cross product
#
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"
    }
}
======

Modified:

======
# crossproduct --
#     Return the "cross product" of two 3D vectors
# Arguments:
#     vect1      First vector
#     vect2      Second vector
# Result:
#     Cross product
#
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 {=}{
            $v12*$v23 - $v13*$v22, 
            $v13*$v21 - $v11*$v23, 
            $v11*$v22 - $v12*$v21
        }]
    } else {
        return -code error "Cross-product only defined for 3D vectors"
    }
}
======
See similar discussion on [expr*]

Edit: I actually think that the prefix {c} might be better than {=}. It stands for either "calculate" or "comma", and is a bit of a call-out to the fact that Tcl is written in C. It is admittedly a bit confusing to see an assignment operator in the middle of commands that are not about assignment. What do you guys think?

----
[FM] I think I'm really not ok for a "c" prefix. it is enough to do :
======
proc tcl::mathfunc::list args {return $args}
======
to handle list naturally with expr, then write :
======
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(
            $v12*$v23 - $v13*$v22, 
            $v13*$v21 - $v11*$v23, 
            $v11*$v22 - $v12*$v21
        )}
    } else {
        return -code error "Cross-product only defined for 3D vectors"
    }
}
======

Because in expr fonction arguments are separated by commas, whereas in Tcl command arguments are separated by spaces, there should be a former analogy between the two constructs when translating from expr language to Tcl language. 

That's why I'd like to be able to write :
======
namespace eval ::tcl::mathfunc {proc {} args {return args}}

proc ::math::linearalgebra::crossproduct { vect1 vect2 } {
    if { [llength $vect1] == 3 && [llength $vect2] == 3 } {
        foreach {v11 v12 v13} $vect1 {v21 v22 v23} $vect2 {break}
        return {=}{(
            $v12*$v23 - $v13*$v22, 
            $v13*$v21 - $v11*$v23, 
            $v11*$v22 - $v12*$v21
        )}
    } else {
        return -code error "Cross-product only defined for 3D vectors"
    }
}
======
so that writing :
======
expr {(e1, e2, e3)}
====== 
would be equivalent to write :
======
list e1 e2 e3
======
This way, a {=} prefix will be enough, since we can create easily lists of expressions
======
set mylist {=}{($x, $y, hypot($x,$y), $x+$y, $x*$y)}
======
But, currently, function with no name is not taken into account into the expr parser, and this writing results in an error « unexpected "," outside function argument list ». Notice that it means that expr allready knows that it is facing a list of arguments. Maybe the parenthesis are even not mandatory.

So, let's imagine we can compose prefixes, and that comma spearated list outside function arguments just return a list.

The, a list of computed arguments can just be written : {{*}=}{express1, expression2, expression3}. e.g :
======
canvas .c
# square of radius 100 center around point of coords (x,y)
.c create rect {{*}=}$x-100,$y-100,$x+100,$y+100 {#}{48 chars}
# To be compared to :
.c create rect [expr {$x-100}] [expr {$y-100}] [expr {$x+100}] [expr {$y+100}] {#}{79 chars, ie 31 chars more !}
======
----
'''[AMB] - 2025-07-03 12:22:43'''

Making a mathfunc for the list command is one solution, but I am aiming for a more elegant solution. For example, with a prefix that does expressions and also argument expansion via comma-separation, you open up the possibility to call commands completely with comma-separated values, similar to calling a math func within an expression. In a sense, it would expose the expr syntax. 

======
.canvas {c}{"create", "rectangle", $w-100, $h-40, $w+100, $h+40}
======

But again, great idea on making “list” a math func. If you wanted to make it more succinct: 

======
proc ::tcl::mathfunc::list {args} {return $args}

proc c {csv} {uplevel 1 "expr {list($csv)}"}

.canvas {*}[c {"create", "rectangle", $w-100, $h-40, $w+100, $h+40}]

======

But I’d prefer to have a prefix that doubles as expansion and math evaluation

[FM] : There is two distincts subjects here : 
   * The 1st is the shorthand {=}, as prefix, at the Tcl parser level; 
   * The 2nd is the improvement of the expr parser :
   ** The 1st one is the one we discussed above : if « comma out of math function argument » => return a list : Maybe it should be TIPed ?
   ** The 2nd one is the one of TIP282, which is made of 2 improvements :
   *** allow multiple instructions separated by semi-colonn
   *** allow assignement operator.
There was long discussions about getting an assignement operator, in the past... It looks a little weird because of the need to quote the variable to be assigned. Whatever... with these 3 improvements, it would look like :
======
proc regular_poly {c Ox Oy r nbVertex {scale 1}} {
   for {set i 0} {$i < n} {incr i} {
      # composed prefix : calculate, then expand
      lappend Coords {{*}=}{
         "R" = $r * $scale;
         "theta" = $i*pi()/$nbVertex;
         $Ox+$R*cos($theta), $Oy+$R*sin($theta)
      } 
   }
   .c create polygon {*}$Coords
}
pack [canvas .c]
regular_poly .c 300 300 200 7
======
To come back to the « expanding the expansion prefix » subject, notice that if ever we had also a prefix {prefixed} that indicate to the proc procedure (if set in its 3rd argument) that it should parse its arguments in a special way, let's say in assigning local variables beyond their prefix annotation, we could write.
======
proc regular_poly {prefixed}{canvas center radius nb_vertex {scale 1} angle} {
   lassign $center Ox Oy
   set R {=}$radius*$scale
   for {set i 0} {$i < $nb_vertex} {incr i} {
      set theta {=}($i/$nb_vertex+$angle/180)*pi()
      # composed prefix : calculate, then expand
      lappend Coords {{*}=}$Ox+$R*cos($theta),$Oy+$R*sin($theta)
   }
   $canvas create polygon {*}$Coords
}
pack [canvas .c]
regular_poly {canvas}.c {center}{300 300} {radius}200 {nb_vertex}7 {angle}{=}180.0/14
======
Of course, other kind of prefixes would be possible to modulate the proc argument parsing.

To be able to prefix the args specification of procs could be the way to enhance argument handling without creating any new command nor introducing incompatibilities.

But we should have a generic command to define them.
======
interp {} prefix create -command proc -argument 2 -prefix prefixed {{args} {
    foreach a $args {
        if {[llength [info annotation $a]] == 2} {
            # default value case
            set annotation [lindex [info annotation $a] 0]
            set defaultvalue [lindex [info annotation $a] 1]
            set $annotation $defaultvalue
        } elseif {[llength [info annotation]] == 1} {
            set annotation [info annotation $a]
        }
        set $annotation $a
    }
}
======
Of course, the Tcl_ProcObj procedure should be made able to use this interp definition on its 2nd argument.

Similary, the 1st and the 3rd argument (the body), could be prefixed, too.
======
interp prefix create -command proc -argnum 3 -prefix mathfunc {{body} {expr $body}}
proc hypothenus {prefixed}{a b} {mathfunc}sqrt(\$a**2+\$b**2)

hypothenus {a}40 {b}30

# Also : Why not use the proc command to produce c code ? Let's try :

inter prefix create -command proc -argument 3 -prefix tcc {{body} {
     set procName [lindex [info level 0] 0]
     set procArgs [info args $procName]
     tailcall tcc::cproc $procName $procArgs [info annotation $procName] $body
}

proc {double}cadd {double a double b} {tcc}{return a+b}
cadd 1.2 2.1
======

[AMB]: 
I am still thinking about the whole annotation concept that does type validation. I’m starting to lean into the [argparse] approach, which handles things like upvar, type validation, and option flags. But as you stated, this thread kinda has evolved into two main topics: better expr syntax and custom prefixes. On the topic of the expr syntax that you brought up, I agree that expr should allow multiple lines and assignment on those lines. Returning multiple values can be handled by adding [::tcl::mathfunc::list] to the core. 

All this said, I think the lowest-hanging fruit (in order of difficulty) to alleviate some of the pain points of [expr] brought up in this discussion are:

1. Add the math function [::tcl::mathfunc::list] to Tcl, which just returns its arguments. This can be trivially done in pure Tcl, and combined with the {*} prefix it can tackle a lot of the issues brought up here. See [exprstar] for more. 

2. Change the syntax of the [set] command to allow for optional assignment with an expression. See [set varName = expr] for the proposed syntax and a spoofed example. 

3. Allow for multiple lines in an expression, with variable assignment, which you mentioned is TIP282.
----
*** Composition of Prefixes ***
'''[AMB] - 2025-07-04 16:44:33'''

Oh, one other thought: I do like your idea of composing prefixes, but I think they should be comma-separated like {2,1}, where the reverse order of prefixes is the order in which they are applied. That way, let’s say we have an expr prefix of {=}, you could combine it with expansion like {*,=}{expr}. So, it would apply expr, then expand.

'''[FM] - 2025-07-05'''
Thanks you to like this idea of prefix composition. 

Intuitively, I was thinking about applying recursively Tcl_ParseBrace to the prefix found in Tcl_ParseCommand, that's why I was proposing to write {{*}=}, to mean : prefix {=} is prefixed by {*}. I admit that I don't find it easy to read, but I was thinking that maybe it's just a matter of habit. 

Another option to achive this prefix decomposition would be to use « Tcl_FindElement », as it is used for the expansion of litterals in Tcl_ParseCommand. In this case, a composed prefix would just be space separated {* =}.

Another option would be simply to juxtaposate the two prefixes {*}{=}. 

But, before adopting a syntax, which is a matter of convention, it's better to think deeply to the principle, to make it clear, to see if it is usefull, and doable.

In fact, with the {*} prefix, I can distinguish at least two manners of composition :
   * successive : the left prefix is applied to the result of the application of the other prefix
   * convolutive : the right prefix is applied repeatively to each element that results from the application of the {*} prefix
For example :
======
# Successive composition :
{=}{*}{1+1 2+2 3+3}
# is equivalent to : 
{=}{1+1} 2+2 3+3
2 2+2 3+3

# Convolutive composition :
{{=}*}{1+1 2+2 3+3}
# equivalent to : 
{=}1+1 {=}2+2 {=}3+3
2 4 6

# Successive composition :
{#}{*}{1 2 3}
# is equivalent to : 
{#}{1} 2 3
2 3
# Convolutive composition :
{{#}*}{1 2 3}
# is equivalent to : 
{#}1 {#}2 {#}3
{*}{}
======

**** Simple prefixes synthesis ****

%|Prefix|context|meaning|example|%
&|{*}|tcl_Parser|expand the argument|eval {*}$L|&
&|{=}|tcl_Parser|apply expr to the argument|set A {=}(1+1}|&
&|{#}|tcl_Parser|discard the argument|set A 1 {#}{set A to 1}|&
&|{(''char'')}|tcl_parser|split by ''char'' and expand| for {(;)}{set i 0; $i<$length ; incr i} {...}|&
&|any other prefix|tcl_Parser|annotate the Token / Tcl_Obj|regular_poly {canvas}.c {center}{300 300} {radius}200 {nb_vertex}7 {angle}{=}180.0/14|&
&|{list}|set (1srt argument)|This var is a list|set {list}L 1 2 3 4|&
&|{list}|set (1srt argument) with parenthesis|take the part2 as a list index |set {list}L(1) val|&
&|{list}|set (2nd argument)|Cast this value as a list and store it into L var|set L {list}$C|&
&|{dict}|set (1srt argument)|This var is a dict|set {dict}D k_1 v_1 ... ... k_i v_i ... ... k_n v_n |&
&|{dict}|set (1srt argument) with parenthesis|take the part2 as a dict key |set {dict}D(k_i) v_j|&&|{dict}|set (2nd argument)|Cast this value as a dict and store it into the D var|set D {dict}$C|&
I can think also to another construct, like ternary operator, or a switch like construct. 

======
set a {?}[info exists b] {:}$b {:}0
set a {?}$b \
{this_value_of_b:}this_value_for_a \
{that_value_of_b:}that_value_for_a \
{:}default_value_for_a
======     

****Effects of prefix composition****
%|Global prefix | First prefix | Second prefix| context | type | meaning |example|%&|{*}{ *}|{*}|{*}| tcl_Parser | successive | expand the argument, then expand the first element of the expanded argument|set {list}L {*}{ *}{{a b} {c d}}; # L is {a b {c d}}|&
&|{{*}*}|{*}|{*}| tcl_Parser | convolutive | expand the argument, then expand each of its elements|set {list}L {{*}*}{{a b} {c d}}; # L is {a b c d}|&
&|{{=}*}|{=}|{*}| tcl_Parser | convolutive| apply expr to each sub-elements while expanding |.c create rect {{=}*}{$Cx-100 $Cy-100 $Cx+100 $Cy+100}|&&|{=}{ *}|{=}|{*}| tcl_Parser | successive | expand, then apply expr to the first argument |.c create line {=}{ *}{{$x1,$y1,$x2,$y2} -fill red}|&
&|{#}{ *}|{#}|{*}| tcl_Parser | successive | expand, then discard the first argument | set {list}M {#}{ *}{1 2 3} ;# M is {2 3}|&
&|{{*}(''char'')}|{*}|(''char'')| {*}|tcl_Parser | convolutive| split by char and expand then expand again | set {list}L {{(,)}*}{{a,b} {c,d} {e,f}}; # L is {a b c d e}|&
&|{{=}(,)}|{=}|{(,)}| tcl_Parser | convolutive| split by comma, the apply expr to each of the argument | {{=}(,)}{1+1, 2+2, 3+3}; # {=}1+1 {=}2+2 {=}3+3 |&

**** Necessary Commands to define prefixes ****
The prefix {*}, {=}, {(''char''), {#} are hardcoded in the Tcl Parser.

Define a new prefix into the interpreter :
======
interp prefix create $interp /prefixName/ /?script?/
======
if ''?script?'' exists then it is previously applied to the prefixed word.
In every case (script or not script), the prefix will result in an annotation (either as a Token TCL_ANNOTED_TOKEN or Tcl_Obj of Tcl_AnnotedObjectType)

Retrieve the annotation of a word
======
# Emulation :
namespace eval INFO {
    proc annotation {e} {
        if {[dict exist ${::Annotations} $e]} {
            return [dict get ${::Annotations} $e]
        }
    }
    namespace export *
    namespace ensemble create
}

proc annotate {prefix value} {
    dict set ::Annotations $value $prefix
    return $value
}

proc SET {var args} {
    regexp {(\w+)\((\w+)\)} $var -> part1 part2
    
    if {[info exist part2]} {
        set var $part1
    }
    set annotation [INFO annotation $var]
    
    switch $annotation {
        list {
            if {![info exist part2]} {
                if {$args ne {}} {
                    uplevel 1 [list set $var [list {*}$args]]
                } else {
                    uplevel 1 [list set $var]
                }
            } else {
                if {$args ne {}} {
                    uplevel 1 [list lset $var $part2 {*}$args]
                } else {
                    uplevel 1 [subst -noc {lindex [set $var] $part2}]
                }
            }
        } dict {
            if {![info exist part2]} {
                if {$args ne {}} {
                    uplevel [list set $var [dict create {*}$args]]
                } else {
                    uplevel 1 [list set $var]
                }
            } else {
                if {$args ne {}} {
                    uplevel 1 [list dict set $var $part2 $args] 
                } else {
                    uplevel 1 [subst -noc {dict get [set $var] $part2}]
                }
            }
        } array {
            if {![info exist part2]} {
                if {$args ne {}} {
                    uplevel 1 [list array set $var $args]
                } else {
                    uplevel 1 [list array get $var]
                }
            } else {
                if {$args ne {}} {
                    uplevel 1 [list set ${var}($part2) $args]
                } else {
                    uplevel 1 [list set ${var}($part2)]
                }
            }
        } default {
            uplevel 1 [list set $var {*}$args]
        }
    }
}

SET [annotate list L] k1 v1 k2 v2; # returns k1 v1 k2 v2
SET [annotate list L](0) ; # -> returns : k1
SET [annotate dict L](k1) ; # -> returns : v1
SET [annotate array A] a1 v1 a2 b2; # returns nothing
SET [annotate array A](a2) ; # returns b2
======