Opened 8 years ago

Closed 6 years ago

#11776 closed enhancement (duplicate)

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

Reported by: Pap Owned by: jason
Priority: minor Milestone: sage-duplicate/invalid/wontfix
Component: misc Keywords:
Cc: Merged in:
Authors: Reviewers: Travis Scrimshaw
Report Upstream: N/A Work issues:
Branch: Commit:
Dependencies: Stopgaps:

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.

Change History (8)

comment:1 follow-up: Changed 8 years ago by 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. 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: Changed 8 years ago by Pap

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 8 years ago by nbruin

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: Changed 8 years ago by 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.

comment:5 in reply to: ↑ 4 Changed 8 years ago by nbruin

  • Milestone changed from sage-4.7.2 to 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 6 years ago by tscrim

  • Reviewers set to Travis Scrimshaw
  • Status changed from new to needs_review

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

comment:7 Changed 6 years ago by tscrim

  • Status changed from needs_review to positive_review

comment:8 Changed 6 years ago by jdemeyer

  • Resolution set to duplicate
  • Status changed from positive_review to closed
Note: See TracTickets for help on using tickets.