Discussion for introducing the hooks to core classes

This thread is related to issue #5040 
<https://github.com/sympy/sympy/issues/5040> and PR #18769 
<https://github.com/sympy/sympy/pull/18769>.


*1. What is the problem?*

Currently, SymPy's Add and Mul are very complicated. There exist various 
type checkings in their flatten method to make them able to handle various 
objects. However, hard-coding like this can be troublesome when new classes 
are added. Not only these methods need to be added with more type 
checkings, it will make the code more and more jumbled.

To relieve this, 'postprocessor' is introduced to Add, Mul, Pow. When the 
instance of these classes is constructed, this 'preprocessor' hook is 
applied to it, transforming the result. (Currently, only matrix uses this 
hook.)
Although this can be used to reduce the complexity in the classes, still it 
has many problems. First, it is not applied when 'evaluate=False' option is 
passed. Second, it cannot take any keyword argument option. Third, the 
instance always needs to go through flatten method first, and only after 
then it is transformed. This is a waste if completely different flatten 
algorithm is to be applied by postprocessor. Finally, because of the same 
reason with third problem, the arguments must be made sure to pass flatten 
methods without raising any error, adding unnecessary restriction to class 
behaviors.


*2. How can we solve it?*

I believe we can solve it by introducing a hook, which is applied before 
any job is done, and also able to take keyword arguments.


*3. What is my proposal?*

My proposal in #18769 <https://github.com/sympy/sympy/pull/18769> is to 
introduce a hook which is, unlike postprocessor, applied to the arguments 
before anything is done. It has these features:

    1) Keyword arguments can be passed, allowing more flexibility.
    2) Averts the arguments to be unnecessarily processed by flatten method.
    3) By checking `_op_priority` of the arguments, determines which hook 
will be used.

The third feature is important. In current SymPy, `_op_priority` determines 
which operator methods will be used when operator (+, *, etc) is applied 
between two objects. This means, if A's _op_priority is 10 and B's is 11, 
then B.__radd__(A), instead of A.__add__(b), is called when A+B is 
executed. So I thought; "If `_op_priority` determines the behavior of 
'A+B', surely it could determine `Add(A, B)` as well!"

This is especially useful when argument of highest priority defines special 
classes, such as MatAdd and MatMul for matrix classes. Suppose we have 
three arguments A,B and C, with their priority A<B<C. When these three are 
added, `C_Add(C, Add(A, B) )` is returned.
Resolving this is simple. Just make a hook function for C:

>>> C_type_args = [i for i in args if isinstance(i, C)]
>>> other_args = [i for i in args if not isinstance(i, C.func)]
>>> other_args_added = Add(*other_args)
>>> return C_Add(C, other_args_added)

See? No type checking is needed.
With this, let's say someone want to add a new class D. `Add(A, B, C, D)` 
will return `C_Add(C, D_Add(A, B, D) )`. Doing this is simple indeed. Set 
D's `_op_priority` so that A<B<D<C, and make a hook function for D:

>>> D_type_args = [i for i in args if isinstance(i, D)]
>>> other_args = [i for i in args if not isinstance(i, D.func)]
>>> other_args_added = Add(*other_args)
>>> return D_Add(D, other_args_added)

Then, running `Add(A, B, C, D)` will call C's hook. Inside it, 
`Add(*other_args)` will call D's hook because it has the highest priority 
among A,B and D, assigning D_add(A, B, D) to `other_args_added`. 
Add.flatten is not called here.
This all works with full expandability, with minimum complexity and 
bypassing unnecessary steps.


*4. Please give your opinion*

My proposal will be one of the many ways to resolve current issue. I'd be 
grateful if you suggest any improvement to my work, or come up with better 
idea.

-- 
You received this message because you are subscribed to the Google Groups 
"sympy" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to sympy+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/sympy/fee15557-d98b-4512-9d26-92203c207cc3%40googlegroups.com.

Reply via email to