Some thoughts on the current state of expressions.
The _ArgumentContainer base class doesn't make much sense. The expression types should be broken down into those expressions with fixed arguments, and n-ary arguments. The Not, Implies, and ITE operators should just have base class Expression, and all of the n-ary operators should have base class _NaryOperator.
The inverted forms (nor, nand, xnor, unequal) are a bit silly. There isn't any reason having them in an expression tree is superior to just wrapping the or/and/xor/equal with a NOT operator. I think I originally added them b/c there was some nice symmetry with the inverse operator and how factoring works. You will see the .invert().factor() in several places. Probably cheaper to just implement an _invert_factor method.
Related to the previous point, currently the NOT operator automatically eliminates "double negatives". For example, it will convert NOT(XNOR(x)) to just XOR(x). Since ExprNot.__new__ should still be used to automatically invert constants and literals, it still makes some sense to reduce NOT(NOT(x)) to just x.
There is a nice benefit to having expressions automatically cache their inverse. That is, X should always have a reference to Not(x).
Simplification needs some work too. It will make the code much cleaner if we stop auto-simplifying trivial argument lists. For example, Or(), and Or(x). Get rid of all the fancy __new__ logic, and just implement all of those simplifications at the end of the simplify method.
[x] Rearrange Expression base classes, s/_ArgumentContainer/_NaryOperator
[x] Get rid of inverted forms
[x] Have expression instances automatically cache their inverse
[x] Eliminate auto-simplification of degenerate N-ary operators.
Some thoughts on the current state of expressions.
The
_ArgumentContainer
base class doesn't make much sense. The expression types should be broken down into those expressions with fixed arguments, and n-ary arguments. TheNot
,Implies
, andITE
operators should just have base classExpression
, and all of the n-ary operators should have base class_NaryOperator
.The inverted forms (nor, nand, xnor, unequal) are a bit silly. There isn't any reason having them in an expression tree is superior to just wrapping the or/and/xor/equal with a NOT operator. I think I originally added them b/c there was some nice symmetry with the
inverse
operator and how factoring works. You will see the.invert().factor()
in several places. Probably cheaper to just implement an_invert_factor
method.Related to the previous point, currently the NOT operator automatically eliminates "double negatives". For example, it will convert NOT(XNOR(x)) to just XOR(x). Since
ExprNot.__new__
should still be used to automatically invert constants and literals, it still makes some sense to reduce NOT(NOT(x)) to just x.There is a nice benefit to having expressions automatically cache their inverse. That is, X should always have a reference to
Not(x)
.Simplification needs some work too. It will make the code much cleaner if we stop auto-simplifying trivial argument lists. For example,
Or()
, andOr(x)
. Get rid of all the fancy__new__
logic, and just implement all of those simplifications at the end of thesimplify
method.Expression
base classes,s/_ArgumentContainer/_NaryOperator