[AMG]: This page discusses TIP 174 [http://tip.tcl.tk/174], "Math Operators as Commands".
TIP 174 adds commands named '''[+]''', '''[-]''', '''[==]''', etc., for the purpose of exposing math operations to the script level without needing to go through [[[expr]]]. These commands live in the '''::[tcl::mathop]''' [namespace]. Functions (as opposed to operators) are in the ::[tcl::mathfunc] namespace.
----
The following table lists all TIP 174 commands and defines their behavior by showing their [expr]ession equivalents. Parentheses are used to indicate associativity.
%| Operation or test| Cmd| 0| 1| 2| 3 or more arguments|%
&| Bitwise negation| ~| err| ~a| err| err|&
&| Logical negation| !| err| !a| err| err|&
&| Arithmetic negation| -| err| -a|&
%|||||||%
&| Sum| +| 0| a| a+b| ((a+b)+c)+...|&
&| Product| *| 1| a| a*b| ((a*b)*c)*...|&
&| Shift left| <<| err| err| a<**>| err| err| a>>b| err|&
%|||||||%
&| Bitwise and| &| -1| a| a&b| ((a&b)&c)&...|&
&| Bitwise inclusive or| <> | 0| a| a<>b| ((a<>b)<>c)<>...|&
&| Bitwise exclusive or| ^| 0| a| a^b ((a^b)^c)^...|&
%|||||||%
&| Numeric equality| ==| 1| 1| a==b| ((a==b)&(b==c))&...|&
&| String equality| eq| 1| 1| a eq b| ((a eq b)&(b eq c))&...|&
&| Numeric inequality| !=| err| err| a!=b| err|&
&| String inequality| ne| err| err| a ne b| err|&
%|||||||%
&| List membership| in| err| err| a in b| err|&
&| List non-membership| ni| err| err| a ni b| err|&
%|||||||%
&| Strict increasing order| <| 1| 1| a****| 1| 1| a>b| ((a>b)&(b>c))&...|&
&| Decreasing order| >=| 1| 1| a>=b| ((a>=b)&(b>=c))&...|&
%|||||||%
The '''-''' command is something of a changeling. In its unary form, it negates its first (and only) argument. For higher arity, it negates every argument ''except'' its first and returns the sum. While this may seem counterintuitive, it does match the behavior of the '''-''' [expr] operator.
Exponentiation ('''**''') is right-associative.
Short-circuit logic ('''&&''', '''||''') isn't supported because it cannot be implemented in Tcl. Tcl always eagerly determines the value of each argument ''before'' passing it to the command. For this, just keep using [expr]:
% expr {1==1 && [puts hello] eq ""}
hello
1
% expr {1==0 && [puts hello] eq ""}
0
% expr {1==0 || [puts hello] eq ""}
hello
1
% expr {1==1 || [puts hello] eq ""}
1
Shift right is arithmetic, not bitwise, meaning that the sign bit is unchanged by the operation. For two's-complement machines, arithmetic and bitwise shift left produce the same results.
----
[AMG]: When I first read the TIP, I wondered why the inequality operators were strictly binary. It seemed to me that they could be more general, that they could test to see if all elements of a list are distinct. In the zero- and one-argument cases, they would return true. In the two-argument case, they'd behave as expected. For three or more arguments, each possible argument pairing would be checked for inequality, e.g. '''(a!=b)&(a!=c)&(b!=c)''' for three arguments.
Today I asked the [Tcl Chatroom], and the answer was that inequality operators are binary-only because the interpretation for other arities is ''not'' obvious; multiple reasonable interpretations exist.
1. Pairwise inequality: '''(a!=b)&(b!=c)&(c!=d)'''
2. Any inequality: '''(a!=b)|(a!=c)|(a!=d)|(b!=c)|(b!=d)|(c!=d)'''
3. Total inequality: '''(a!=b)&(a!=c)&(a!=d)&(b!=c)&(b!=d)&(c!=d)'''
In the above table, every operator that ''does'' support three or more arguments (I'll call them ternary+ operators) has a commonly understood meaning when written '''a op b op c op d''', for instance '''a == b == c == d'''. Never is '''a != b != c != d''' written because it would not be clear which of the above three interpretations is intended.
Moreover, for every ternary+ operator, interpretations (1) and (3) always yield the same result, assuming the list ordering isn't reversed when using asymmetric operators. This is because they all exhibit transitivity: '''(((a op b) and (b op c)) implies (a op c))'''. Try it and see. :^)
[RS] pointed out that (2) is simply '''[[! [[== a b c d]]]]'''. He also suggested a nice, simple implementation of (3) using [[[lsort]]]:
proc distinct {args} {== [llength $args] [llength [lsort -unique $args]]}
(Use the '''-real''' [lsort] option if you don't want "0", " 0", "0.0", "-0", etc. to be considered distinct.)
I can't imagine a single case where (1) is useful, but it wouldn't be difficult to implement.
[wdb] In my humble opinion, in opposite to the operator ==, the operator != ''cannot'' be transitive. Watch this infix notation:
0 != 1 != 0
You can translate it to ''0 != 1 && 1 != 0'', but would you ever say because part 1 is true and part 2 is true, that the conclusion were ''0 != 0''?
[AMG]: Yes, the whole problem is that != isn't transitive, which is why the ambiguity exists. '''[[!= 0 1 0]]''' is true according to interpretations (1) and (2) but false according to interpretation (3). For any transitive operator, (1) and (3) always yield the same result.
----
[wdb] At moment, the lazy evaluating operator ''&&'' cannot be implemented. Just an idea ... in the form above, it is not possible to realise ''&&'' with lazy evaluation as every arg is evaluated when the function is invoked. But if we braced the list of args? Watch:
&& {expr1 expr2 ...}
results in the same bytecode as
expr {expr1 && expr2 ...}
See also the ternary operator ''?'' - [RS] ... which is of course [if]...''else''...
[AMG]: Would this behavior (automatic application of [expr]) be applied to all math operator commands or only to '''||''' and '''&&'''? The problem I see is that it would effectively make quoting required for all arguments, same as it is with [[[expr]]] itself. See [brace your expr-essions] for the reasons why.
[wdb] Both of you ([RS] and [AMG]) are right. One of my [quick'n'dirty] ideas. What if the operator (e.g.) ''+'' as well as ''&&'' would concat and evaluate their arguments as well as nowadays [expr]? Example:
&& $expr1 $expr2
should be equivalent to
&& {$expr1 $expr2}
In the latter case of example above, $expr2 is not evaluated if $expr1 results in [false]; in the former case, both are evaluated. The result should be the same unless $expr1 is false and $expr2 produces some side effect such as an error (but not only). I am not sure if I have overseen some pitfalls. But is this idea good? Or bad?
[AMG]: Bad, I think, but I only say so because it gets in the way of the use I envision for operator commands. I find bracing my [expr]essions to be a chore, and I look forward to not having to do this when using math operator commands. Combine this with the ::[tcl::mathfunc] [namespace], and I won't need [[[expr]]] at all. I'd have to use [[[if]]] and friends to make up for the lack of conditionals and short-circuit evaluation, but this is fine because I rarely use these outside of the first argument to [[[if]]] and [[[while]]].
By the way, this change makes me much less interested in '''$[[...]]''' shorthand for '''[[[expr] {...}]]'''. I simply won't need it.
[wdb] But note that [expr] is part of the core as it is invoked implicitly as arg#1 for [if], [while], and arg#3 for [for]. -- Generally there comes some new dichotomy as ''expr {$a + $b}'' and ''+ $a $b'' do the same job.
----
[LES] I hope that TIP 174 never makes it to the core. That is all perfectly possible with sugar syntax. My position is: don't change the language except to implement something that is really not possible with the existing syntax and commands. If you want everything prêt-à-porter, make your own procs and classes and '''reuse''' them in every opportunity, or just code in [PHP] and its 1,000+ TV dinner functions.
[AMG]: Perhaps something similar to TIP 174 can be placed in [tcllib] to make it commonly available.
[Lars H]: You hope in vain, '''LES'''; TIP#174 is already (2007-01-13) part of the 8.5 core. An important reason for having it is that the commands already exist a bytecode operations, so its implementation was only about adding direct Tcl commands that compile to these things without taking the detour through the [expr] [little language].
----
[PWE] Wouldn't it be nice if the commands were also able to handle lists, and process them as array's or vectors?
[RS]: See [vector] for links to a number of such extensions/code snippets. If they treat a scalar like the original operator, it might indeed be possible to overload math operators in that way...
[Lars H]: A good thing about having the math operations as commands in the tcl::mathop namespace is that you can easily have corresponding operations on vectors (defined the way ''you'' want them) in another namespace. In Tcl, one picks a command made for the type of data one wishes to operate on, rather than making commands that try to guess which operation should be applied based on the data they're fed.
----
[AJD] A couple of nice-to-have builtin operator commands would be: [[<=>]] (numeric comparison) and [[cmp]] (string comparison) as Perl has. These crop up when writing sort comparison commands. Of course you can write
proc <=> {a b} { if {$a > $b} { return 1 } ; if {$b > $a} { return -1 } ; return 0 }
but that's no different to any of the other op's recently introduced.
[RS]: If I understand right, ''cmp'' is what Tcl calls [string compare]; a <=> could be done as sgn($a-$b), where the classic definition by [rmax] is
proc sgn x {expr {($x>0)-($x<0)}}
[DKF]: Surely you mean:
proc tcl::mathfunc::sgn x {tcl::mathop::- [tcl::mathop::> $x 0] [tcl::mathop::< $x 0]}
[RS] Do I? Maybe as a compromise, with [namespace import]ed operators:
proc tcl::mathfunc::sgn x {- [> $x 0] [< $x 0]}
But still I think, an [expr] with two or more different operators in play looks better (clearer) than a nest of prefix operators...
[LV] AJD, how do your <=> and cmp differ from Tcl [expr]'s == and eq?
[AJD] ...see the above proc definition for [[<=>]]. While, as has already been noted, [[cmp]] is really just [[string compare]]. It should be obvious how they differ to == and eq: for example [[== 0 1]] is 0 while [[<=> 0 1]] is -1. The typical usage would be in a sort compasion routine:
proc my_cmp {a b} {
set int_a [do_something_that_returns_an_integer $a]
set int_b [do_something_that_returns_an_integer $b]
return [<=> $int_a $int_b] ;# or perhaps more readably as: [expr {$int_a <=> $int_b}]
}
set sorted [lsort -command my_cmp $some_list]
[LV] I see - you are right, those are useful functions. So, the next question is this - can one use the [namespace ensemble] functionality in Tcl 8.5 to write cmp and <=>, in a way that, as a result, expr would have them?
[AMG]: [[cmp]] and [[<=>]] can ''not'' be made into new ''operators,'' not without modifying the Tcl source code. TIP 174 change 3 says "the commands are not connected to their corresponding expr operator. Overloading or adding any command in ::tcl::mathop does not affect operators in expr or any other command that calls Tcl_ExprObj, and nor does overriding expr alter the behaviour of any command in :::tcl::mathop."
But they can be made into new ''functions.'' Put them in the '''::[tcl::mathfunc]''' namespace and they'll be visible as '''cmp(a, b)''' and '''<=>(a, b)'''. However, I don't think [[[expr]]] will like that last name, so call it '''sign''' or '''sgn'''.
----
KD: It is unfortunate that the ternary ? operator was not included in TIP 174. When I look at my programs, almost half of the cases where I could use "operators as commands" involve the ternary operator. Usually for cases like:
$w configure -state [expr {$s ? "normal" : "disabled"}]
which becomes much cleaner when written as:
$w configure -state [? $s normal disabled]
The TIP states that the ternary operator was not included because it has lazy evalution semantics in expr (when properly braced), but that is not a problem IMHO.
[RS] On the other hand, as [if] is a function that returns the result of the succeeding script, how about
proc I x {set x} ;# identity
$w configure -state [if $s {I normal} {I disabled}]
KD: Clever, didn't think about this. But I think it looks still better with the ? operator.
[AMG]: Going a step farther, you're free to make your own "ternary operator" command. Note that I brace the first argument to [[if]] because it is an [expr].
proc ? {test true false} {if {$test} {return $true} {return $false}}
I often find myself using '''?:''' with more than just three arguments, for example (C):
int i = input();
int o = i == 0 ? 99 : i == 1 ? 98
: i == 2 ? 97 : i == 3 ? 96
: i == 4 ? 95 : 94;
Writing this using [[?]] might yield the following (Tcl):
set i [input]
set o [? [== $i 0] 99 [? [== $i 1] 98\
[? [== $i 2] 97 [? [== $i 3] 96\
[? [== $i 4] 95 94]]]]]
To avoid excess brackets, a more general '''?''' operator can be made that would work like [[if ... elseif ... elseif ... else]]:
proc ? {args} {
if {[llength $args] % 2 != 1} {
error "wrong # args: should be \"? ?test1 out1 test2 out2 ...? default\""
}
while {[llength $args] > 2} {
set args [lassign $args test true]
if {$test} {return $true}
}
return [lindex $args end]
}
I don't use the math operator commands in the above code because all math is done inside the first argument to [[[if]]] or [[[while]]] which already understands the [expr] language.
Rewritten example:
set i [input]
set o [? [== $i 0] 99 [== $i 1] 98\
[== $i 2] 97 [== $i 3] 96\
[== $i 4] 95 94]
Unlike with C, all the comparisons are performed ''before'' invoking the '''?''' operator. If the tests have side effects, this is a problem. But I prefer to not have side effects in my tests, so it's okay with me, and the only remaining problem is the slight performance decrease.
It is possible to give [[?]] the power of short-circuit evaluation simply by removing the braces from its [[[if]]] test, and then all test arguments to [[?]] will need to be braced to avoid immediate evaluation. But for this facility to support variables and such, an [[[uplevel]]] is needed too.
To make the short-circuit evaluation behavior complete, do the same for the values returned by [[?]], and brace every argument to [[?]].
proc ? {args} {
if {[llength $args] % 2 != 1} {
error "wrong # args: should be \"? ?test1 out1 test2 out2 ...? default\""
}
while {[llength $args] > 2} {
set args [lassign $args test result]
if [list [uplevel [list expr $test]]] "return \[uplevel [list expr $result]\]"
}
set result [lindex $args end]
return [uplevel [list expr $result]]
}
Safely constructing scripts to pass to [[[uplevel]]] can be a pain, but I think I successfully avoided [quoting hell]. (Tcl offers spiritual liberation! Put ''that'' in the man page!)
Usage example:
% set x 5
% ? {$x == 5} {$x * 2} {$x == 6} {$x * 3} {$x - 1}
10
% set x 6
% ? {$x == 5} {$x * 2} {$x == 6} {$x * 3} {$x - 1}
18
% set x 7
% ? {$x == 5} {$x * 2} {$x == 6} {$x * 3} {$x - 1}
6
KD: When you take it this far, you cannot return strings anymore. So the example we started from ($w configure -state [[? $ s normal disabled]]) won't work anymore.
[AMG] interrupts: Actually you can return strings; you just have to use [expr] notation to do so.
$w configure -stat [? {$s} {"normal"} {"disabled"}]
I don't advocate this usage; I am merely demonstrating a possible implementation in response to queries about '''[[?]]''' and lazy/short-circuit evaluation. But that's just me; someone may find it to be just the right thing.
KD continues: You can indeed implement operators like these yourself (see also [Modeling COND with expr]), but I'm a bit reluctant about defining single-symbol procs myself. Therefore it would have been a good idea to standardize the ? operator in TIP 174 with its usual (ternary) meaning.
[Lars H]: Yet another alternative to a ? command is [lindex]
$w configure -state [lindex {normal disabled} $s]
(This assumes $s is 0 or 1, but one can coerce other booleans using ::tcl::mathfunc::[bool].)
----
[LV] 2007 July 10
Maybe I don't understand the table at the top. My assumption, reading it, is that the columns are the name of the operation, the symbol used for the operation, what is returned if the symbol is used with 0 arguments, 1 argument, and 2 arguments, and finally an example of the operator in use.
However, at least two of the table entries confuse me:
Product * 1 a a*b ((a*b)*c)*...
Exponentiation ** 1 a a**b a**(b**(c**...))
What does it mean that a * or ** used with 0 arguments return a 1 and that
used with one argument return the argument?
Why, at least in the 0 argument case, is the result not an error like nearly every other operator?
It's not a ''compatibility with expr'' thing:
$ tclsh
% set a [expr *]
missing operand at _@_
in expression "_@_*"
% puts $a
can't read "a": no such variable
Looking again over the table, I see quite a few cases where the operator is returning a value in the case of 0 or 1 argument where there are what seems to me to be arbitrary values being returned, rather than consistency. Again, if it were to be compatible with expr it would be understood. Otherwise, it seems like an explanation of why someone would do this would be helpful.
[AMG]: '''[[+]]''' evaluates to zero because the sum of zero numbers is defined to be zero, since zero is the additive identity. Extrapolating from that uncontroversial idea, '''[[*]]''' and '''[[**]]''' evaluate to one because the product of zero numbers is one, since one is the multiplicative identity. Viewed another way, these are the basis cases for the respective operations, and each input is (iteratively/recursively/whatever) applied to an accumulator which is initially set to the operator's respective identity.
Sum : ((((0)+a)+b)+c)+...
Product : ((((1)*a)*b)*c)*...
Exponentiation: a**(b**(c**(...**(1))))
(The position of the (1) for exponentiation is due to right associativity.)
If that doesn't have you convinced, consider that the same concept applies to the bitwise operators:
Bitwise AND ('''&''') clears the nth bit in its output iff at least one of its inputs has its nth bit cleared. Therefore the AND of zero inputs is all-bits-set (-1). None of the inputs have any bits cleared, so none of the output bits are cleared. '''[[&]]''' evaluates to -1.
Similarly, bitwise OR ('''|''') sets the nth bit in its output iff at least one of its inputs has its nth bit set. Therefore the OR of zero inputs is all-bits-cleared (0); none of the inputs have any bits sets, so none of the output bits are set. '''[[|]]''' evaluates to 0.
Bitwise XOR ('''^''') is also similar. The nth output bit is set iff an odd number of inputs have their nth bits set. If zero inputs have their nth bits set, the nth output bit is cleared because zero is not an odd number. If there aren't any inputs, by this definition all output bits must be cleared. '''[[^]]''' evaluates to 0.
(also [AMG], added after [escargo]'s comment but not in response to his comment) 100% [[[expr]]] compatibility is not the goal; since infix notation is not being used, operators are not limited to two arguments. To maximize utility, their definitions have been generalized to support as many arities as possible. When given two arguments (or one, in the case of the three negation operators), they behave the same as in [[[expr]]].
''[escargo]'' - Perhaps some of these make sense for compatibility with other languages ([J], [APL], [Scheme],
[Lisp]). Or they are intended to be the appropriate identity element for the operation for vacuously true
cases (like the empty product [http://en.wikipedia.org/wiki/Empty_product]).
----
[AMG]: FGP@124.43.70.163's edit on 2008-09-17 05:35:24 tab-damaged the hell out of the table at the top. I just restored it to the previous version. I think this has happened before. Someone care to investigate? Is it a browser problem, a server problem, a database problem, or a bad interaction between two or all three?
[Lars H]: What happens is that the data you get in the edit box seems to have undergone a `[[string map {" " \t} ...]]` (sequences of eight spaces are mapped to a tab), and this wrecks ASCII graphics because browsers treat tabs as "jump to the next tab stop position", which is only eight columns further to the right if you are already at a tab stop. Copying data, doing the reverse [string map] on it, and pasting the result back fixes the problem — it will be correct when you save it. As I recall, I have confirmed using [sockspy] that the tabs were present in the data coming from the server, so it isn't a browser problem, and it only happens in edits.
Now why on earth someone has put a malfeature as brain-damaged as that in the server is beyond me, but it should indeed be fixed. If you have the Google account needed to file a bug report against [WubWikit] then perhaps you could do the honours? (I lost mine some time ago, and was at the time not quite enough annoyed by this bug to bother getting a new one.)
----
!!!!!!
%| [Category Language] | [Category Mathematics] |%
!!!!!!
**