Aaron said:
So you want to separate the canonicalizers from both the objects and
the containers?  It seems to me that it would be best to just put them
in the objects themselves, probably just using the Python double
dispatch system, but this is an interesting idea too.  Would that make
it more or less extensible?

I would prefer to have them (the canonicalizers(my spell checker
insists that the word does not exist)) outside of the objects. I
imagine them just as a special case of crawlers (very similar to what
crawlers the python AST module supports).

Ronan said:
How is that different from the existing expression trees? If you really
have *syntax* trees in mind, then it's not a suitable solution since you
can't take advantage of the commutativity of addition or of the
associativity of multiplication (syntactically, "a + b" and "b + a" are
different things).
I'm not sure what you mean by canonicaliser, but I'll assume you refer
to Add(*foo) being used to compute the sum of the elements of foo. I
agree that it's a problem and a violation of the single-responsibility
principle. We should replace Add(*foo) with an efficient function
ssum(foo).

When I say canonicalizer I mean the algorithm that converts eg
"2*x+1+x+3" to "4+3*x". I don't want commutativity in + or
associativity in *. I want them to be containers. Then, (just giving
an **example**, not necessary my vision of the future) when the AST is
constructed a default canonicalizer is chosen. A way to do this will
be:
0. parse the input and create AST (basically just sympify(..., evaluate=False))
1. call the ChoseGoodCanonicalizer crawler on the AST
2.a. ChoseGoodCanonicalizer returns canonicalizer implementating of
our current algorithms for trees containing only symbols
2.b. it returns another canonicalizer if the AST tree contains stranger things.

This does not directly fix the problem with two people subclassing
Expr for different purposes and then trying to mix those two. But
different canonicalizers can be called only on subtrees. Now as it
stands this is impossible. If I have MatrixSymbols A and B and ket K I
can not do A*B*K and expect good results.

I am sure that my lack of education in CS is preventing me to see a
better solution, that is why I am shooting those ideas here: some of
you should be able to correct me. But separating container and
canonicalizer (hopefully I have defined the word in this mail) seems a
good idea.

Stefan

On 9 March 2012 19:55, Ronan Lamy <ronan.l...@gmail.com> wrote:
> Le vendredi 09 mars 2012 à 09:50 +0100, Joachim Durchholz a écrit :
>> Wearing my software architect hat:
>>
>> Am 09.03.2012 01:51, schrieb Aaron Meurer:
>> > So you want to separate the canonicalizers from both the objects and
>> > the containers?  It seems to me that it would be best to just put them
>> > in the objects themselves, probably just using the Python double
>> > dispatch system, but this is an interesting idea too.
>>
>>
>> Multiple dispatch or multiple inheritance?
>
> In Python, double dispatch refers to the complicated mechanism by which
> binary operators get evaluated (e.g. when you have the expression
> "a + b"). That reminds me that I started to write an explanation of how
> it really works, I ought to finish it some day. Meanwhile, see
> http://docs.python.org/library/numbers.html#implementing-the-arithmetic-operations
>
> I don't really get Aaron's meaning either, though.
>
>>
>> Multiple dispatch (i.e. selecting the function to be executed based on
>> the type of more than one parameter) makes extending the system via
>> subclasses unmodular: people cannot extend independently, they must
>> coordinate.
>>
>> Assuming that X' is a subclass of some class X,
>> if we have a function f(A,B),
>> and some person writes A' and defines f(A',B),
>> and somebody else writes B' and defines f(A,B'),
>> and a third person wants to use both A' and B', and calls f(A',B'), that
>> call is ambiguous, it could go to the A' or the B' variant of f.
>>
>> In most likelihood, both are wrong since the A' and the B' variants of f
>> were written because the new subclasses needed new code in f.
>> So if somebody wants to combine A' and B', he needs to "fill the matrix"
>> with a new f(A',B').
>
> I'm not sure multiple dispatch is the problem. Calling f[X, Y] the
> implementation of f for instances of the classes X and Y, then if both
> A' and B' obey Liskov substitutability, f[A', B](a', b') == f[A, B'](a',
> b') == f[A, B](a', b'), so there is no ambiguity in what the value of
> f(a', b') should be.
>
>>
>> It would be possible to do this inside SymPy. Just document which
>> functions are using multiple dispatch, and warn potential subclass
>> authors that overriding these functions requires coordination with
>> anybody writing a subclass on another parameter.
>>
>>
>> Multiple inheritance as done in Python is unsound in the presence of
>> diamond inheritance.
>>
>> I have an essay on the topic; see
>> http://durchholz.org/essays/object-orientation/diamond-inheritance/
>>
>> IMNSHO, Python's C3 MRO is better than the original mechanism but still
>> hopelessly inadequate.
>> In general, this is not a big problem. However, SymPy would be
>> particularly vulnerable, since it is dealing with a hierarchy of
>> mathematical concepts, with abundant diamond structures (actually, the
>> essay uses hypothetical Group and Monoid classes to explain the
>> difficulties).
>
> A few comments on your essay:
> * You obviously meant 'Ring' instead of 'Group'.
> * It doesn't apply to Python: in Python, you can't rename inherited
> methods and 'a = d' can never mean 'hammer square peg d into round hole
> a'.
> * There is a categorical oversight in your analysis: in mathematical
> terms, 'Monoid' and 'Ring' are categories, while specific algebraic
> structures (e.g. the ring of integers (ZZ, +, *)) are members of these
> categories. The instances of the classes you discuss are elements of
> these structures (e.g. the integer 42), so the classes themselves
> represent the structures. In Pythonic terms, Monoid and Ring should be
> metaclasses, which means you don't have a diamond any more, since B and
> C are instances of the same metaclass Monoid, but don't share a common
> base class.
>
>>
>>  > Would that make it more or less extensible?
>>
>> More extensible, until the mechanisms are in widespread use, then it
>> will start becoming less extensible.
>>
>> My advice would be to stick with
>> - single dispatch
>
> We can't. "a + b" necessarily invokes some form of double dispatch.
>
>> - single inheritance
>> - finding ways to structure the logic so that these two are enough
>> - where that does not work (and we will have that), program some
>> explicit mechanism for resolving what needs to be done in what case.
>>
> Hm, you're just saying that we should avoid multiple inheritance and
> multiple dispatch. But the critical question is "How?" Could you take an
> existing example of multiple inheritance, for instance AtomicExpr, and
> explain what you think we should with it?
>
>>
>> I hope this has shed some light on the issues.
>> I'm aware that this were some pretty strong statements on a sometimes
>> controversial topic. Feel free to ask questions, or to challenge
>> assumptions and conclusions.
>>
>> Regards,
>> Jo
>>
>
>
> --
> You received this message because you are subscribed to the Google Groups 
> "sympy" group.
> To post to this group, send email to sympy@googlegroups.com.
> To unsubscribe from this group, send email to 
> sympy+unsubscr...@googlegroups.com.
> For more options, visit this group at 
> http://groups.google.com/group/sympy?hl=en.
>

-- 
You received this message because you are subscribed to the Google Groups 
"sympy" group.
To post to this group, send email to sympy@googlegroups.com.
To unsubscribe from this group, send email to 
sympy+unsubscr...@googlegroups.com.
For more options, visit this group at 
http://groups.google.com/group/sympy?hl=en.

Reply via email to