Opened 11 years ago

Closed 10 years ago

# Holding an expression unevaluated: Something like hold_all() would be nice.

Reported by: Owned by: Panagiotis Papasotiriou Jason Grout minor sage-duplicate/invalid/wontfix misc Travis Scrimshaw N/A

### Description

A function that holds its arguments unevaluated, something like

```hold_all(expression)
```

would be very nice, if implemented. It would be very useful when teaching Sage to students, and in general it would facilitate printing results together with the unevaluated expression.

Please have a look at http://test.sagenb.org/home/pub/6, it is a worksheet that explains (in detail, I hope) how a function like hold_all() would be useful in practice.

### comment:1 follow-up:  2 Changed 11 years ago by Nils Bruin

Given that `expression` is already evaluated (leading to a simplified expression) before `hold_all` even gets a hold of it, this approach is a little problematic. The basic feature is already available, but perhaps not very convenient for your taste. See `sin(pi,hold=True)`. Currently this isn't available for integrals etc., but there is `sage.calculus.calculus.dummy_integrate(x^2+1,x)` It is very reasonable to ask for `integrate(x^2+1,x,hold=True)` to be synonymous with that.

You can already do

```def held(f):
return lambda *args: f(*args,hold=True)
sin=held(sin)
cos=held(cos)
sin(pi)^2+cos(pi)^2
```

one could have a package inert_symbolic_functions that has (essentially) those declarations, so that a

```from held_symbolic_functions import *
```

would give you a "held" environment. With a little namespace injection magic one could probably also use that to implement

```with held_function_context:
expr=sin(pi)^2+cos(pi)^2
```

which is probably the closest to what you request, subject to being doable in python.

### comment:2 in reply to:  1 ; follow-up:  3 Changed 11 years ago by Panagiotis Papasotiriou

Replying to nbruin:

Given that `expression` is already evaluated (leading to a simplified expression) before `hold_all` even gets a hold of it, this approach is a little problematic.

I know, that's why I suggested a different approach in the end of the Sage worksheet I posted (http://test.sagenb.org/home/pub/6). Something like

```unevaluated_expr=hold_all( integrate(x^2+1,x) + diff(tan(x),x) )
```

then a command to evaluate it, if needed, say

```evaluated_expr=evaluate(unevaluated_expr)

```

So one could pretty print the unevaluated expression, together with the the evaluated one with

```join(["\$",unevaluated_expr,"=",evaluated_expr,"\$"])

```

Note that such a functionality is pretty much standard in many Computer Algebra systems. Maxima, for example, suspends evaluation if an expression is preceded by a single quote, and has a very convenient function called `ev()` to evaluate the expression (or even parts of it) later on, so one could use

```unevaluated_expr : '( integrate(x^2+1,x) + diff(tan(x),x) )
evaluated_expr : ev(unevaluated_expr)
print(unevaluated_expr,"=",evaluated_expr)
```

where everything inside `'(...)` is not evaluated. The same thing can be done in Mathematica and even yacas. I was actually surprised Sage doesn't have an easy way to do the same.

The basic feature is already available, but perhaps not very convenient for your taste. See `sin(pi,hold=True)`. Currently this isn't available for integrals etc., but there is `sage.calculus.calculus.dummy_integrate(x^2+1,x)` It is very reasonable to ask for `integrate(x^2+1,x,hold=True)` to be synonymous with that.

Well, the very basic feature is already available, indeed, but not for every function and not in a way one could call "convenient". Furthermore, `integrate()` was just an example. I didn't know about the package `dummy_integrate` (and I have no means to know which functions accept `hold=True` and which don't). But anyway, it only solves the issue for that particular (and very simple) example. I am thinking of something more general and more powerful.

You can already do` def held(f): return lambda *args: f(*args,hold=True) sin=held(sin) cos=held(cos) sin(pi)^2+cos(pi)^2 ` one could have a package inert_symbolic_functions that has (essentially) those declarations, so that a ` from held_symbolic_functions import * ` would give you a "held" environment. With a little namespace injection magic one could probably also use that to implement ` with held_function_context: expr=sin(pi)^2+cos(pi)^2 ` which is probably the closest to what you request, subject to being doable in python.

Well, doable, but sounds like reinventing the wheel, in my humble opinion. Not to mention it works only for functions that accept `hold=True`, and I have no idea how I will suspend evaluation of operators that way.

Given the fact that Sage makes heavy use of LaTeX's power to typeset expressions perfectly (while Maxima or Mathematica either don't use LaTeX, or use it only with the aid of external programs), it is a pity we can't use that power to create educational worksheets, pretty much like books, where printing an expression unevaluated together with the corresponding evaluated one is a must. It is actually one of the first things I try to do as a test in every CAS I am learning.

### comment:3 in reply to:  2 Changed 11 years ago by Nils Bruin

Replying to Pap:

I know, that's why I suggested a different approach in the end of the Sage worksheet I posted (http://test.sagenb.org/home/pub/6). Something like

```unevaluated_expr=hold_all( integrate(x^2+1,x) + diff(tan(x),x) )
```

That is exactly what can't work. This is equivalent to the python code

```unevaluated_expr=hold_all(integrate(x^2+1,x).__add__(diff(tan(x),x)))
```

The meaning of this in python is that what is inside the parentheses gets executed and the result of that gets passed to `hold_all`. By the time `hold_all` executes, it is already too late. You need to inform `integrate`, `__add__` and `diff` that they need to behave differently from what they would normally do. The "hold" parameter does that, but as you observe, it is rather burdensome that it potentially has to be supplied to all routines involved (the fact that not all relevant routines accept "hold" yet is just a matter of a bug to fix).

Basically what is needed is a flag on SR that sets "hold=True" to be the default rather than "hold=False". I don't know how easy and how thread-safe such a flag would be. It would definitely violate the stipulation that parents be immutable.

A "clean" solution would be to allow a second instance of SR that does have "hold=True" as default? This would allow something that you'll probably find painful too:

```sage: SRheld = SymbolicRing( hold_by_default = True )
sage: SRheld(1) + SRheld(3) #note the need to turn 1,3 into symbolic objects before adding
1 + 3
sage: x = SRheld.var('x')
sage: integrate(x^2+1,x)
integral(x^2+1,x)
sage: xt = SR(x) # the normal SR still behaves as before
sage: integrate(xt^2+1,xt)
1/3*x^3+x
```

I'm afraid the proposed namespace magic I proposed earlier will never fully work because `SR(1).__add__(3)` can't be reached that way. So we need to find a convenient place to store the "hold default value". SR itself would be a reasonable place except that changing the value definitely changes how SR behaves and parents are supposed to be immutable.

Perhaps if we make a context manager that sets and resets a "hold_by_default" flag, we localize the potential trouble a bit. `localvars` has set a precedent for such:

```with held_function_context(SR):
expr=sin(pi)^2+cos(pi)^2
```

If in addition it would hold a lock on SR we would be thread safe as well (just not very thread friendly).

I think there is merit in having "hold" facilities more readily available but to implement it requires some serious architectural considerations and likely some relatively comprehensive modifications.

### comment:4 follow-up:  5 Changed 11 years ago by Karl-Dieter Crisman

Does it look like #10035 is what you are asking for? I'm not sure whether these are dups, though it seems like they might be.

### comment:5 in reply to:  4 Changed 11 years ago by Nils Bruin

Milestone: sage-4.7.2 → sage-duplicate/invalid/wontfix

Replying to kcrisman:

Does it look like #10035 is what you are asking for? I'm not sure whether these are dups, though it seems like they might be.

Yes! two people come up independently with the same solution. I think a context is the most convenient practical solution.

I'm reassigning the ticket to "duplicate" and put it up for review. (I think that is the procedure?) If someone else confirms the "dup" status they can give it a positive review. Otherwise, just revert the milestone and revert to "new status.

### comment:6 Changed 10 years ago by Travis Scrimshaw

Reviewers: → Travis Scrimshaw new → needs_review

I also agree that this is a duplicate of #10035.

### comment:7 Changed 10 years ago by Travis Scrimshaw

Status: needs_review → positive_review

### comment:8 Changed 10 years ago by Jeroen Demeyer

Resolution: → duplicate positive_review → closed
Note: See TracTickets for help on using tickets.