sagemath / sage

Main repository of SageMath
https://www.sagemath.org
Other
1.36k stars 462 forks source link

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

Closed cad43853-1bda-41ed-87e5-48bceb105534 closed 11 years ago

cad43853-1bda-41ed-87e5-48bceb105534 commented 13 years ago

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.

Component: misc

Reviewer: Travis Scrimshaw

Issue created by migration from https://trac.sagemath.org/ticket/11776

nbruin commented 13 years ago
comment:1

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.

cad43853-1bda-41ed-87e5-48bceb105534 commented 13 years ago
comment:2

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 dodef held(f): return lambda *args: f(*args,hold=True) sin=held(sin) cos=held(cos) sin(pi)<sup>2+cos(pi)</sup>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)<sup>2+cos(pi)</sup>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.

nbruin commented 13 years ago
comment:3

Replying to @sagetrac-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.

kcrisman commented 13 years ago
comment:4

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.

nbruin commented 13 years ago
comment:5

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.

tscrim commented 11 years ago

Reviewer: Travis Scrimshaw

tscrim commented 11 years ago
comment:6

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