On Fri, Jul 20, 2012 at 12:08 PM, Aaron Meurer
<reply@reply.github.com>
wrote:
>> @@ -1686,6 +1596,72 @@ def expand(e, deep=True, modulus=None, power_base=True, power_exp=True, \
>>      (x*y + y**2)/(x*(x + 1))
>>      >>> expand((x + y)*y/x/(x + 1), denom=True)
>>      y*(x + y)/(x**2 + x)
>> +
>> +    API
>> +    ===
>> +
>> +    Objects can define their own expand hints by defining
>> +    ``_eval_expand_hint()``.  The function should take the form::
>> +
>> +        def _eval_expand_hint(self, deep=True, **hints):
>> +            if deep:
>
> Right.  This has nothing to do with the order of the traversal.  It's because the traversal happens *only once*.
>
> I'm not opposed to putting some logic in expand() to intelligently call expand multiple times for various hints to make sure expressions are completely expanded.  But I am opposed to the hint methods trying to do this, because what it leads to is the mess we had before where expand was called far more times than it needed to be, and quite often, and infinite number of times.

Having come from the previous headache that was expand, I understand
why you are shy about this now. What leads to infinite recursion is
doing the same thing over and over. But you wouldn't be doing that
with the following:

```
def expand(...):
    if deep and obj.args:
        obj = obj.func(*[a.expand(...) for a in obj.args])
    for hint in hints:
        if hasattr(obj, _eval_expand_hint):
            obj = obj._eval_expand_hint(*hints)
    return obj

def _eval_expand_hint(self, *hints):
    """only apply this hint non-recursively to the object
    The full hints dictionary is available in case there are metahints
that should
    be used to govern the application of this hint.
    """
    return expanded_self
```

I didn't realize until now how similar subs and expand are. This is
the same sort of approach taken in subs. The user only writes an
`_eval` routine if it makes sense (e.g. only log will have an
`_eval_expand_log` definition). The `_eval` routines do exactly one
thing: apply the expansion to the top level expression. This makes
them very easy to write, get right, and maintain.

One area of confusion (and perhaps area to agree on) is that perhaps a
distinction needs to be made between recursive and deep. Elsewhere in
sympy, deep refers to entering functions where here deep means "enter
args". But perhaps I want mul to be applied recursively without
entering functions. In this case -- when recursive is True -- expand
itself (not the `_eval` routines) would handle the recursion.

```
def expand(obj, ...):
    old = obj = sympify(obj)
    if deep and obj.args:
        obj = obj.func(*[a.expand(...) for a in obj.args])
    for hint in hints:
        if hasattr(obj, _eval_expand_hint):
            obj = obj._eval_expand_hint(*hints)
    if recursive and old != obj:
        return expand(obj)
```

That recursive step is the place where oo recursion can occur because
in the unlikely case (for expansion, but not "simplification") that an
expansion of one expression leads back to an already seen expression
you would hit a loop. But I'm not sure this can happen with expansion
that makes expressions longer in terms of op count.

See what you think. I think we are really close to getting this right this time.

/c