Version 2 of Faking tcl::mathop::&& and ||

Updated 2014-01-25 21:00:17 by PeterLewerin

The set of tcl::mathop commands does not include commands corresponding to the && or || for reasons that are, well, reasonable. You can still (easily) fake them on lists of values that are a subset of the general set of boolean values, namely 0 (falsity) and all non-zero numerical values (truth). (You can also fake them on general boolean values, but not as easily or conveniently.)

Any boolean expression evaluates to these values, or can be trivially converted to one of these two values by applying the logical negation (NOT, !) twice:

set foo [list true 1 [expr {5 > 2}]]
# => true 1 1
set bar [lmap elem $foo { expr {!!$elem} }]
# => 1 1 1

This means that we can use tcl::mathop::* to perform a multi-argument AND operation on these values:

::tcl::mathop::* {*}$bar
# => 1
::tcl::mathop::* [expr {5 > 2}] [expr {5 > 4}] [expr {5 > 7}]
# => 0

since the semantics of multiplication corresponds to the semantics of conjunction (AND) when the domain of boolean values is defined this way. Even in the case where there are no arguments provided the semantics are a fit, since the multiplicative and the conjunctive identities are both 1.

An important caveat is that there is no short-circuit evaluation: every argument is evaluated.

Multi-argument OR can also be faked:

::tcl::mathop::+ {*}$bar
# => 1
::tcl::mathop::+ [expr {5 > 12}] [expr {5 > 14}] [expr {5 > 7}]
# => 0

since the semantics of addition corresponds to the semantics of inclusive disjunction (OR) when the domain of boolean values is defined this way. Even in the case where there are no arguments provided the semantics are a fit, since the additive and the disjunctive identities are both 0.

An important caveat is (as above) that there is no short-circuit evaluation: every argument is evaluated.Commands can be implemented for these operations in this way (but again, for every set of boolean values that aren't the boolean literals on, true, etc, the tcl::mathop::* and + commands are sufficient):

proc listAnd args {
    # Performs serial logical conjunction without short-circuit evaluation.
    # Every argument shall be a pre-evaluated boolean value.
    # If given at least one false value, returns 0, otherwise (including if given no values) returns 1.
    ::tcl::mathop::* {*}[lmap arg $args { expr {!!$arg} }]
}

proc listOr args {
    # Performs serial logical inclusive disjunction without short-circuit evaluation.
    # Every argument shall be a pre-evaluated boolean value.
    # If given at least one true value, returns 1, otherwise (including if given no values) returns 0.
    expr {[::tcl::mathop::+ {*}[lmap arg $args { expr {!!$arg} }]] > 0}
}

% listOr [expr {5 > 12}] [expr {5 > 14}] [expr {5 > 7}]
# => 0
% listOr [expr {5 > 12}] [expr {5 > 4}] [expr {5 > 7}]
# => 1
% listAnd [expr {5 > 2}] [expr {5 > 4}] [expr {5 > 7}]
# => 0
% listAnd [expr {5 > 2}] [expr {5 > 4}]
# => 1

See also