Opened 4 years ago

Last modified 4 years ago

#17958 new defect

implement declare_var, deprecate (None)var

Reported by: rws Owned by:
Priority: major Milestone: sage-6.6
Component: symbolics Keywords:
Cc: nbruin, kcrisman Merged in:
Authors: Reviewers:
Report Upstream: N/A Work issues:
Branch: Commit:
Dependencies: Stopgaps:

Description (last modified by jdemeyer)

Functions returning a value should not have side effects, var does. In #17447, comment 23 Nils Bruin proposed to separate both usages of var by introducing declare_var: this should behave exactly like var without return value, and var should not put the var handle in the globals list but should act like SR.var. If we want the following behaviour:

  1. declare_var('x') == var('x') as before but returning None
  2. var('x') prints deprecation message, returns variable as before; error after deprecation period
  3. y = var('x') as before (but without globals), NO deprecation message

we certainly need the preparser to recognize 2/3, and to replace (2) with declare_var('x'); deprecation(...); x and (3) with y = SR.var('x'). Secondly, there is a different docstring needed with var when compared with declare_var.

This and the same with functions is the most annoying problem for people doing calculus in Sage.

Change History (69)

comment:1 Changed 4 years ago by rws

  • Summary changed from implement declare_var to implement declare_var, deprecate (None)var

comment:2 Changed 4 years ago by rws

  • Cc nbruin added
  • Description modified (diff)

comment:3 Changed 4 years ago by mmezzarobba

Does sage really need a version of var() that touches the global namespace? Other similar functions like polygen() and *.gen() don't do it, except for a handful of constructors after inject_on()—which I doubt anyone uses. And y = var('y') is clearer and shorter than declare_var('y'), though it would be nice if y = var() (or perhaps <y> = var()?) was preparsed to y = SR.var('y').

comment:4 Changed 4 years ago by rws

Not really need but:

sage -t src/sage/symbolic/expression.pyx  # 424 doctests failed
sage -t src/sage/symbolic/assumptions.py  # 51 doctests failed
sage -t src/sage/symbolic/callable.py  # 48 doctests failed
sage -t src/sage/calculus/riemann.pyx  # 126 doctests failed
sage -t src/sage/calculus/calculus.py  # 81 doctests failed
sage -t src/sage/calculus/tests.py  # 29 doctests failed
sage -t src/sage/calculus/wester.py  # 29 doctests failed
sage -t src/doc/en/prep/Advanced-2DPlotting.rst  # 24 doctests failed
sage -t src/doc/en/prep/Quickstarts/Multivariable-Calculus.rst  # 38 doctests failed
sage -t src/doc/de/thematische_anleitungen/sage_gymnasium.rst  # 31 doctests failed
sage -t src/sage/functions/piecewise.py  # 25 doctests failed

That's those over 20 in symbolic,calculus,doc,functions.

EDIT: grep for 'sage: var(' shows 338 hits in 67 files...

Last edited 4 years ago by rws (previous) (diff)

comment:5 follow-up: Changed 4 years ago by rws

I should have looked sooner: the catch is, I removed the globals injection in calculus/var.pyx:var() and the variables get still injected. And indeed the same with SR.var(), so that has side effects too and is no cure for the matter.

EDIT: Noo, that's wrong, it's the expression on the lhs that gets injected!

I still have to find a case where injection of the variable itself matters.

Last edited 4 years ago by rws (previous) (diff)

comment:6 Changed 4 years ago by mmezzarobba

I think I don't understand what you mean:

sage: SR.var('xyz')
xyz
sage: xyz
...
NameError: name 'xyz' is not defined

comment:7 in reply to: ↑ 5 Changed 4 years ago by rws

I still have to find a case where injection of the variable itself matters.

Sorry, I should say... where injection via globals in the var function makes a difference versus ex = SR.var(...), where presumably globals is filled by iPython with the ex.

comment:8 follow-ups: Changed 4 years ago by vdelecroix

The name var by itself makes a lot of confusion. There are Python variables which are a very different concept. What about calling it symbol or Symbol as it is done in sympy?

Vincent

comment:9 in reply to: ↑ 8 ; follow-up: Changed 4 years ago by egourgoulhon

Replying to vdelecroix:

The name var by itself makes a lot of confusion. There are Python variables which are a very different concept. What about calling it symbol or Symbol as it is done in sympy?

+1

Eric.

comment:10 in reply to: ↑ 9 Changed 4 years ago by tmonteil

Replying to egourgoulhon:

Replying to vdelecroix:

The name var by itself makes a lot of confusion. There are Python variables which are a very different concept. What about calling it symbol or Symbol as it is done in sympy?

+1

Eric.

+1 as well, the name "variable" causes a lot of troubles to newcommers that use var() to declare Python names, see for example this list of ask questions. Instead of a generic "variable", we should use "symbol" for SR, "name" (for Python), "indeterminate" for polynomials. This will help learning Sage a lot.

By the way, note the difference between

sage: SR.var('x,y')
(x, y)

and

sage: SR.symbol('x,y')
x,y
sage: SR.symbol('x y')
x y

So perhaps should we also make a difference between Symbol and Symbols ?

Last edited 4 years ago by tmonteil (previous) (diff)

comment:11 follow-ups: Changed 4 years ago by vbraun

SR.var does exactly what the declare_var does without the drawbacks of an additional global, so as it stands I'm against the ticket description.

SR.symbol and SR.var should probably be aliases but aren't. I wasn't even aware of SR.symbol, which is why I only fixed the comma parsing in SR.var in #7496.

How about something like R.<x> = SR() to get symbolic generators?

comment:12 in reply to: ↑ 11 Changed 4 years ago by mmezzarobba

Replying to vbraun:

How about something like R.<x> = SR() to get symbolic generators?

Yes, that's probably better that the syntax I suggested.

comment:13 in reply to: ↑ 8 ; follow-up: Changed 4 years ago by mmezzarobba

Replying to vdelecroix:

The name var by itself makes a lot of confusion. There are Python variables which are a very different concept. What about calling it symbol or Symbol as it is done in sympy?

Definitely not Symbol(). As for symbol(), it could have been a better name choice than var(), but I doubt switching to it now is a good idea. The benefits are not that significant, and since var() is proabably one of the most widely used functions outside the sage tree itself (in particular, in random code snippets), the compatibility break would be a pain for many of people (the recent deprecation of pol.coeffs() already was pretty bad from this point of view).

comment:14 in reply to: ↑ 13 ; follow-ups: Changed 4 years ago by tmonteil

Replying to vbraun:

SR.symbol and SR.var should probably be aliases but aren't. I wasn't even aware of SR.symbol, which is why I only fixed the comma parsing in SR.var in #7496.

Actually SR.var is a wrapper over SR.symbol that splits commas and spaces to create tuples of symbols.

How about something like R.<x> = SR() to get symbolic generators?

This is non-pythonic, requires additional preparsing, needs to create the name R while the ring SR is already here, and will add even more confusion to newcomers that hardly understand the difference between a symbolic expression like x^2+1 and a well defined polynomial over a well defined ring.

Replying to mmezzarobba:

Definitely not Symbol(). As for symbol(), it could have been a better name choice than var(), but I doubt switching to it now is a good idea. The benefits are not that significant, and since var() is proabably one of the most widely used functions outside the sage tree itself (in particular, in random code snippets), the compatibility break would be a pain for many of people (the recent deprecation of pol.coeffs() already was pretty bad from this point of view).

The benefits are very significant for newcomers and anyone that interacts with newcomers, i personally spent a huge amount of time to deal with that issue (on ask.sagemath.org (see my previous link for a small sample) but also during tutorials).

Sage is full of inconsistencies, refusing to clean them because they are used increases the entry cost and will eventually lead to an obscure language where each function/method has its own semantics. This is not long-term viable. This is why we have a deprecation policy. For such a function, we could make the deprecation message more verbose and pedagogical than usual.

comment:15 in reply to: ↑ 14 Changed 4 years ago by rws

Thierry, I agree fully but:

How about something like R.<x> = SR() to get symbolic generators?

This is non-pythonic, requires additional preparsing, needs to create the name R while the ring SR is already here, and will add even more confusion to newcomers that hardly understand the difference between a symbolic expression like x^2+1 and a well defined polynomial over a well defined ring.

But it would be consistent and allow different ring types (later). Actually declare_var could be provided additionally.

comment:16 in reply to: ↑ 14 ; follow-up: Changed 4 years ago by mmezzarobba

Replying to tmonteil:

This is non-pythonic, requires additional preparsing, needs to create the name R while the ring SR is already here, and will add even more confusion to newcomers that hardly understand the difference between a symbolic expression like x^2+1 and a well defined polynomial over a well defined ring.

I don't care much about "pythonicity", but what Volker suggests would be consistent with the rest of Sage. And since this is all for interactive use anyway, I don't see the problem with using the preparser, nor with writing _.<x> = SR().

From a pedagogical point of view, it might actually be a good thing to make it clearer that var() (or, to be precise, symbol()) is more or less the same as gen(), only for SR.

Sage is full of inconsistencies, refusing to clean them because they are used increases the entry cost and will eventually lead to an obscure language where each function/method has its own semantics. This is not long-term viable.

I tend to agree in general, but I am not convinced in this particular case. Having a longer function name is inconvenient, and I find symbol() only marginally clearer (if at all) than SR.var(). (I'm fine with either removing var() entirely or making it equivalent to SR.var(), however.)

This is why we have a deprecation policy.

The deprecation policy is a joke... Except perhaps for a few _-functions, just about anything in sage can be considered public, but only few changes are considered worth a deprecation.

For such a function, we could make the deprecation message more verbose and pedagogical than usual.

Yes, but please keep in mind that it will pop up everywhere for a long time.

comment:17 in reply to: ↑ 11 Changed 4 years ago by nbruin

Replying to vbraun:

SR.var does exactly what the declare_var

No, it does not:

  • SR.var a symbol and does not inject anything.
  • var (toplevel) as it exists now returns a symbol and injects a binding to it
  • declare_var as proposed would inject a binding to a symbol and return None.

The problem with the mixed actions of var is that people learn it, see it returns a symbol, and then use it in circumstances where they need a symbol returned. That works, but behind the scenes there is also a binding injected. That then surprises them later.

If we have two routines, one that only returns a symbol and the other that only injects a binding, this potential for surprise is eliminated.

I think we do want the possibility of injecting something, because x=var('x') or x=SR.var('x') is too verbose (and more importantly, requires the x to by typed twice).

We need the injection capability on toplevel because this is one of the first things that novices need to be able to do. I'm not completely sure we need an entry to SR.var in the global namespace. However, the capability has been there as part of var (with sometimes surprising side-effects) so I think we pretty much have to continue it.

SR.symbol and SR.var should probably be aliases but aren't. I wasn't even aware of SR.symbol, which is why I only fixed the comma parsing in SR.var in #7496.

They probably shouldn't. At least one of them should be 'make a symbol with the given print name or raise an error'. The fact that the return type of var depends on the formatting of the string (either a symbol or a tuple of symbols) is a wart that stems from convenience for the injection purpose.

How about something like R.<x> = SR() to get symbolic generators?

Cute, but I think it misses the mark for the intended audience: complete novices. It makes it very hard to convince people that Sage is a reasonable choice relative to Maple and Mathematica (and Maxima), where you can just start using a symbol.

comment:18 in reply to: ↑ 16 ; follow-up: Changed 4 years ago by nbruin

Replying to mmezzarobba:

I don't care much about "pythonicity", but what Volker suggests would be consistent with the rest of Sage. And since this is all for interactive use anyway, I don't see the problem with using the preparser, nor with writing _.<x> = SR().

From a pedagogical point of view, it might actually be a good thing to make it clearer that var() (or, to be precise, symbol()) is more or less the same as gen(), only for SR.

It is not consistent with the rest of sage and hard to implement, since presently it amounts to _ = SR(names=('x',)); (x,) = _._first_ngens(1) Normally, calling a constructor with different names gives different results:

sage: PolynomialRing(QQ,names=('x',)) == PolynomialRing(QQ,names=('y',))
False

and we would need to hack SR._first_ngens to remember the last set of generators that got returned.

The scenario really doesn't fit in the current meaning of _.<..>=..., neither in implementation nor in semantics.

comment:19 in reply to: ↑ 18 Changed 4 years ago by mmezzarobba

Replying to nbruin:

It is not consistent with the rest of sage and hard to implement, since presently it amounts to _ = SR(names=('x',)); (x,) = _._first_ngens(1) Normally, calling a constructor with different names gives different results:

sage: PolynomialRing(QQ,names=('x',)) == PolynomialRing(QQ,names=('y',))
False

and we would need to hack SR._first_ngens to remember the last set of generators that got returned.

I mean from a UI point of view. Otherwise, sure, it would require the preparser to repeat the names when asking for the generators, or something similar.

Is there another variant that you like better?

comment:20 follow-up: Changed 4 years ago by jhpalmieri

Remember that defining a new mathematical variable might be the first thing that a new Sage user will want to do, so from a UI point of view, _.<x> = SR() is a disaster. It looks like a meaningless string of symbols. var('x') or declare_var('x') or symbol('x') or similar at least have a chance to indicate some meaning when someone glances at the code/worksheet/notebook.

Maybe something like math_variable('x') would convey what Sage is doing here, and in particular will distinguish this from Python variables.

Last edited 4 years ago by jhpalmieri (previous) (diff)

comment:21 in reply to: ↑ 20 Changed 4 years ago by mmezzarobba

Replying to jhpalmieri:

Remember that defining a new mathematical variable might be the first thing that a new Sage user will want to do, so from a UI point of view, _.<x> = SR() is a disaster. It looks like a meaningless string of symbols. var('x') or declare_var('x') or symbol('x') or similar at least have a chance to indicate some meaning when someone glances at the code/worksheet/notebook.

Well, then, form that point of view, I find x = SR.var('x') much better. It clarifies in particular (i) that you are assigning an object to a certain Python variable, and (ii) that the indeterminate you are creating belongs to a particular parent--often not the one you want if you are using sage in the first place!

comment:22 follow-up: Changed 4 years ago by jhpalmieri

Re SR.var('x'): "What does SR mean?" "The Symbolic Ring." "What's a ring?"

Remember that we have users who just want to do calculus. They don't know what a ring is. They also are not that familiar with Python, and we shouldn't use this particular situation to educate them on Python syntax. So I think we need a top-level function. The proposed declare_var('x'), which returns None but injects the variable into the global namespace, seems like the more natural behavior for novices. (We can have two functions, as Nils says, one like this and a second one which does not inject anything but returns the symbol. I would suggest advertising the first of these in the tutorial and other parts of the documentation. )

The name declare_var could maybe be improved because of the different uses of the word "variable". Something like declare_math_var? declare_math_symbol? new_math_symbol?

comment:23 in reply to: ↑ 22 ; follow-up: Changed 4 years ago by mmezzarobba

Replying to jhpalmieri:

Re SR.var('x'): "What does SR mean?" "The Symbolic Ring." "What's a ring?"

Remember that we have users who just want to do calculus. They don't know what a ring is. They also are not that familiar with Python, and we shouldn't use this particular situation to educate them on Python syntax.

I doubt you can use sage (and not shoot yourself in the foot on every possible occasion) without understanding this kind of things at least a little. And for sure I've seen intelligent people with a very reasonable level in math, use sage in teaching while completely misunderstanding how basic things work... because, at first, they just wanted to do calculus, so they were led to use things like var('x') without understanding what they did, and basically assumed that names in sage had the same kinds of semantics as in maple.

The name declare_var could maybe be improved because of the different uses of the word "variable". Something like declare_math_var? declare_math_symbol? new_math_symbol?

declare_symbolic_variable perhaps, if you really feel such a function is useful?

comment:24 in reply to: ↑ 23 ; follow-up: Changed 4 years ago by nbruin

Replying to mmezzarobba:

I doubt you can use sage (and not shoot yourself in the foot on every possible occasion) without understanding this kind of things at least a little.

You can do some very simple examples, such as differentiating a function, plotting one, trying to compute an antiderivative without understanding the way python names (really, python has "names" in its namespaces. Variables have other connotations) and SR symbols interact; sort of the level of "wolfram alpha". We have to give people at that level at least a way into sage, otherwise they don't even get to shoot themselves in the foot, experience that as unpleasant and then gain the motivation to learn how to avoid that in the future.

There's a reason why maple, mathematica, maxima went with their approach. We can't quite do that, but we have to make the hurdle as low as possible. I think

var('x')

or

declare_var('x')

are about the best we can do. I think it's a problem they return something in addition to injecting a binding. If we need to produce feedback on the action taken, I think printing something would be preferable (it's a routine that's only meant to be used interactively anyway), so perhaps:

sage: declare_var('x,y')
Declaring x, y as symbolic variables
sage: A=declare_var('z')
Declaring z as a symbolic variable
sage: A
None
sage: declare_var('w',quiet=True)
sage:

(where the quiet would be the gateway to getting people to use other ways--perhaps we shouldn't provide that)

I'd be completely OK with declare_var being spelled as var too. The main thing is that I think it has been shown that injecting as well as returning something is harmful, so I hope we can our change our interface to not do that.

Last edited 4 years ago by nbruin (previous) (diff)

comment:25 in reply to: ↑ 24 Changed 4 years ago by nbruin

Last edited 4 years ago by nbruin (previous) (diff)

comment:26 follow-up: Changed 4 years ago by tmonteil

declare_var sounds like varibale declaration that exists in many languages and is completely different that what is done here. There is already a lot of newcomer's code that start with

var('a')
a=2

So i can imagine how much more there will be with declare_var, since it will add confusion to programmers too.

What is currently discussed is a function that injects a symbol into the namespace, so inject_symbol sounds more meaningful as it just tells what is actually done.

As for the Symbolic Ring, it is perhaps better not to know what a mathematical ring is, so perhaps thinking about that object as The Lord of the Rings is not so bad. We do not have much "calculus" course in France so i can not tell much about the benefits of playing with objects we are not able to define, i can just witness from what i see on ask.sagemath.org that people get more lost by lack of clear definitions than by excess (e.g. how to factor a polynomial if we do not know on which ring it is defined?).

I agree that in any case, both injecting and returning is harmful (function does that too).

comment:27 in reply to: ↑ 26 ; follow-up: Changed 4 years ago by nbruin

Replying to tmonteil:

What is currently discussed is a function that injects a symbol into the namespace, so inject_symbol sounds more meaningful as it just tells what is actually done.

Except that "inject" is rather technical and not what the novice thinks about doing. You'd probably have to explain to them: "Before using y, you have to declare that it is a math symbol, which you do by declare_symbol("y")."

I indeed agree that using "symbol" instead of "symbolic variable" would be better to distinguish the concept from a python name. However, we've been calling these things "var" since the start of sage, so changing that may be painful for our current users.

I agree that in any case, both injecting and returning is harmful (function does that too).

There the terminology is even worse: python actually calls its "def" and "lambda" objects "functions". So "declare_function" will be even more confusing than "declare_var".


A minimal plan is to either not have var produce side-effects, meaning

sage: var('z')
z
sage: z+1
Error

or let var return None, meaning

sage: z = var('z')
sage: z+1
Error

If neither one is palatable then we can stop the discussion now. We're stuck with a bad design decision, for which the pain for repairing it is too high.


An alternative is to migrate to the unspoilt

sage: z = symbol('z')
sage: declare_symbol('w')
sage: z+w+1
z+w+1

and let var rot and fester, trying to nudge people away from it (put a deprecation on it after a while?). In 10 years or so we could think of removing var.


For function we can perhaps do the deprecation in-place. That's mainly going to affect people trying to solve ODEs and PDEs (outside of that, symbolic (formal/abstract) functions don't have much use)

comment:28 Changed 4 years ago by kcrisman

  • Cc kcrisman added

comment:29 in reply to: ↑ 27 ; follow-up: Changed 4 years ago by egourgoulhon

Replying to nbruin:

An alternative is to migrate to the unspoilt

sage: z = symbol('z')
sage: declare_symbol('w')
sage: z+w+1
z+w+1

and let var rot and fester, trying to nudge people away from it (put a deprecation on it after a while?). In 10 years or so we could think of removing var.

IMHO, this should be the good strategy. declare_symbol is a bit long however and could be replaced by simply symbol, while the above symbol could be replaced by something like SR.get_symbol. Indeed, the end user has hardly the need of z = symbol('z') (am I right ?), so replacing it by something "sophisticated" like z = SR.get_symbol('z') seems fine. To summarize, the above code would become

 sage: z = SR.get_symbol('z')
 sage: symbol('w')  # what end users really need
 sage: z+w+1
 z+w+1

comment:30 in reply to: ↑ 29 ; follow-up: Changed 4 years ago by nbruin

Replying to egourgoulhon:

Indeed, the end user has hardly the need of z = symbol('z') (am I right ?),

Searching our current codebase and examples indicates you might not be right in that assumption. I didn't do a precise count, but the occurrences of a=var('a') and c,d=var('c,d') are quite frequent (half of the var occurrences maybe?), so there's definitely an immediate need for it to do an automatic replacement.

The fact that this developed in the first place also suggests that a significant number of people were not aware of/did not trust the injecting behaviour of var, so it's probably "surprising" behaviour (it's certainly non-pythonic to go and scribble in the globals dictionary). That indicates to me that the routine doing it needs a name that makes explicit it's having a side effect. A normal way of doing that is making the name a verb or verbal phrase, hence declare_var or declare_symbol.

For obtaining a symbol as a return value a noun or nominal phrase should be OK, hence var or symbol.

comment:31 Changed 4 years ago by vbraun

  • Why not SR.gen('x') to create a new variable instead of SR.symbol / SR.var? Also: currently broken, so would be nice to fix.
  • To inject variables into the namespace, why not attach a method ex.inject_variables() to symbolic expressions that injects the output of ex.variables().

At the end of the day, I don't think that the purity of functional programming conventions is worth the pain of changing how var behaves. Yes it does chafe against your OCD. But imagine you don't know Python and just want to do some symbolic stuff. I can guarantee you that that user is not going to appreciate your lesson in functional programming.

comment:32 in reply to: ↑ 30 ; follow-up: Changed 4 years ago by kcrisman

Searching our current codebase and examples indicates you might not be right in that assumption. I didn't do a precise count, but the occurrences of a=var('a') and c,d=var('c,d') are quite frequent (half of the var occurrences maybe?), so there's definitely an immediate need for it to do an automatic replacement.

The fact that this developed in the first place also suggests that a significant number of people were not aware of/did not trust the injecting behaviour of var, so it's probably "surprising" behaviour (it's certainly non-pythonic to go and scribble in the globals dictionary). That indicates to me that the routine doing it needs a name that makes explicit it's having a side effect. A normal way of doing that is making the name a verb or verbal phrase, hence declare_var or declare_symbol.

Well, of course another reason is that in doctests it can be annoying to do

sage: var('a')
a

while

sage: a=var('a')
sage:

seems cleaner. Maybe it's not all due to confusion.

comment:33 in reply to: ↑ 32 ; follow-up: Changed 4 years ago by nbruin

Replying to kcrisman:

Well, of course another reason is that in doctests it can be annoying to do

sage: var('a')
a

while

sage: a=var('a')
sage:

seems cleaner. Maybe it's not all due to confusion.

I'm not quite clear whether you mean:

  • side-effectful routines should NOT return a value (quite standard) and should NOT print something.
  • side-effectful routines are unclean anyway and it's no bother to type a=var('a').

comment:34 in reply to: ↑ 33 ; follow-up: Changed 4 years ago by kcrisman

seems cleaner. Maybe it's not all due to confusion.

I'm not quite clear whether you mean:

  • side-effectful routines should NOT return a value (quite standard) and should NOT print something.
  • side-effectful routines are unclean anyway and it's no bother to type a=var('a').

What I mean is that I think the doctests were written that way because it was easier to typea=var('a') than have to deal with an output. For myself, I think that

  • keeping previous behavior
  • ease of use

argue strongly for var('a') or something else easy, no equals signs etc. In fact, var(a) would be easiest but Python wouldn't allow that.

comment:35 in reply to: ↑ 34 Changed 4 years ago by rws

Replying to kcrisman:

...var(a) would be easiest but Python wouldn't allow that.

We're not so picky when it comes to other preparsed stuff.

comment:36 follow-ups: Changed 4 years ago by vbraun

How about

sage: var a, b

handled by the preparser. Its not a function, so it alleviates the side effect concern. The var statement would only be allowed at the beginning of the line (just like the print statement), so you wouldn't be able to assign anything anyways. And its easily handled by the preparser as '^[\s]*var ' regex.

comment:37 in reply to: ↑ 36 Changed 4 years ago by jdemeyer

Replying to vbraun:

How about

sage: var a, b

Note that SageMathCloud supports

sage: %var a, b

for this.

comment:38 in reply to: ↑ description Changed 4 years ago by jdemeyer

Replying to rws:

  1. var('x') prints deprecation message, returns variable as before; error after deprecation period
  2. y = var('x') as before (but without globals), NO deprecation message

I think it's technically impossible that var('x') and y = var('x') behave in a different way.

comment:39 follow-up: Changed 4 years ago by mmezzarobba

Then what about doing the following:

  1. Implement %var a, b (or perhaps var a, b) and/or SR.var('a').inject(), leaving var() alone for the moment.
  2. Change as much as possible of the documentation and examples to use either a = SR.var('a') or %var a.
  3. Wait a year or two to see if the new syntax catches on.
  4. Formally deprecate var(), add a deprecation warning, a remove it after a while.

comment:40 Changed 4 years ago by rws

Please change the description accordingly if no one has serious arguments (I think it's optimal). If asked about %var/var my purely personal preference would be the latter.

comment:41 in reply to: ↑ 39 ; follow-up: Changed 4 years ago by nbruin

Replying to mmezzarobba:

  1. Implement %var a, b (or perhaps var a, b) and/or SR.var('a').inject(), leaving var() alone for the moment.
  2. Change as much as possible of the documentation and examples to use either a = SR.var('a') or %var a.

You'd need to take into account that the %var processing is done by the REPL. So for notebook, ipython and the doctest framework (and any new interfaces that arise) you'd have to provide it.

By making var not a function you're also blocking off reasonable programmatic use. Presently:

sage: var(''.join('x%d '%i for i in [1..10]))
(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10)

This is comparable to why in Python3 print was turned into a function.

Otherwise I like the missing quotes in the syntax; I dislike having to explain what the modulo or string formatting sign is doing at the start of a line when you're explaining to someone that sage is "just like python" (should they know that already).

SR.var('a').inject() has problems. The incantation is obviously atrocious to type. But also: note that inject() would simply be a method on an expression. Would it inject all variables that occur in it?

comment:42 in reply to: ↑ 41 ; follow-up: Changed 4 years ago by vbraun

Replying to nbruin:

By making var not a function you're also blocking off reasonable programmatic use.

Exactly, and that is IMHO a big plus of the proposal.

Even right row you are not supposed to use var in library code, but there is nothing stopping you. Programmatically generated symbolic variables should always be declared as x = SR.var('x'). This is already spelled out in the current var docstring.

comment:43 Changed 4 years ago by vbraun

PS: The Sage doctests are preparsed but not run in IPython. So var a would work in a doctest, whereas %var a would not.

comment:44 Changed 4 years ago by jdemeyer

  • Description modified (diff)

comment:45 Changed 4 years ago by jdemeyer

NOTE: I am making changes to the implementation of var() in #18083.

comment:46 in reply to: ↑ 42 Changed 4 years ago by nbruin

Replying to vbraun:

Even right row you are not supposed to use var in library code, but there is nothing stopping you. Programmatically generated symbolic variables should always be declared as x = SR.var('x'). This is already spelled out in the current var docstring.

Is that spelled out? I looked at sage.calculus.var.var? (that's the var that also occurs at top-level) and didn't find it there. I don't think a programmatic approach to injecting x1,...,x10 is so bad.

A possible scenario:

sage: A=var(''.join('x%d '%i for i in [1..10]))
sage: L=sum(A[i]^(i+1) for i in [0..9])^2
sage: L.expand().coefficient(x4^4)
2*x10^10 + 2*x9^9 + 2*x8^8 + 2*x7^7 + 2*x6^6 + 2*x5^5 + 2*x3^3 + 2*x2^2 + 2*x1

which actually illustrates a genuine use of "inject as well as return". D'oh.

comment:47 follow-up: Changed 4 years ago by vbraun

The var docstring (var?) contains

   Note: The new variable is both returned and automatically
     injected into the global namespace. If you need symbolic variable
     in library code, it is better to use either SR.var() or
     SR.symbol().

comment:48 Changed 4 years ago by jdemeyer

See also #18084.

comment:49 in reply to: ↑ 47 Changed 4 years ago by nbruin

Replying to vbraun:

The var docstring (var?) contains

   Note: The new variable is both returned and automatically
     injected into the global namespace. If you need symbolic variable
     in library code, it is better to use either SR.var() or
     SR.symbol().

Yes that's for use in the library. The problem with %var would be feeding programmatically generated input into it. I don't think that's currently explicitly discouraged in the documentation.

Reducing support for that would be a reduction in functionality. If the advantages of %var are otherwise overwhelming we could still decide to go that route, but it shows that the spelling with quotes does have its advantages too.

comment:50 follow-up: Changed 4 years ago by vbraun

Oh you mean the good old var(', '.join(['x{0}'.format(i) for i in range(10)])) trick. Calculus freshmen are going to love your class... In any case I don't mind having a way to inject multiple variables at once, but something like

sage: SR.inject_variables('x, y')    
sage: SR.inject_variables('x', 'y')   # strings to variable names
sage: SR.inject_variables('x{i}', i=range(10))    # use keyword arguments to format
sage: SR.inject_variables(ex)     # inject all of ex.variables()

would probably be a lot better.

comment:51 in reply to: ↑ 36 Changed 4 years ago by kcrisman

How about

sage: var a, b

Something along these lines would MASSIVELY help with this issue. As long as we have to declare variables anyway, we should at least make it easy to do so, and the syntax currently is kind of hard to type

var('z,y')

(try this on a qwerty board slowly to see all the unusual movements due to the shifts and non-home row things) so it would definitely be so for a beginner.

As long as there is a LONG deprecation period for this (as it's likely to bite quite a few people who wouldn't upgrade very frequently) something along these lines seems fine, return value None or whatever seems appropriate. It would also be great to have %var eventually since SMC does but that could be a different ticket as long as there is a good deprecation period to var('a').

comment:52 Changed 4 years ago by jdemeyer

I would favor an "infinite" deprecation period for var(): deprecate it but keep supporting it forever.

comment:53 Changed 4 years ago by kcrisman

Seems reasonable.

comment:54 in reply to: ↑ 50 Changed 4 years ago by nbruin

Replying to vbraun:

sage: SR.inject_variables('x, y')    
sage: SR.inject_variables('x', 'y')   # strings to variable names
sage: SR.inject_variables('x{i}', i=range(10))    # use keyword arguments to format
sage: SR.inject_variables(ex)     # inject all of ex.variables()

Something like that would work, but probably not under that name. The method already exists on SR by inheritance (and doesn't work), and the signature you're proposing is incompatible with the one on other rings.

Injecting in general isn't really a method that belongs on the object, since the object doesn't naturally have access to the dictionary into which these things should be injected. It's really more the task of a REPL utility function. In which case the spelling

inject_generators(QQ['x'])

would make more sense. The magic of figuring out into which dictionary the bindings should be injected is compartmentalized into a single function which could be implemented basically as

def inject_generators(parent):
    D={repr(a): a for a in parent.gens()}
    print "defining ",D.keys()
    user_globals.update(D)

This probably much nicer than scattering references to user_globals all over the place (we'd probably want to add some sanity checks to prevent this from inserting objectionable bindings).

For symbolic binding we could then have something along the lines of

def inject_symbols(*args):
    user_globals.update({ repr(a):a for e in args for a in e.variables()})

Interfacing via %var is then an additional measure.

In short, what we seem to be converging towards is:

  • deprecate sage.calculus.var.var (but keep supporting it; after a while probably do adorn it with a deprecation warning)
  • support symbolic variable injection via a special %var directive (which saves quotes too!) -- Is the % a problem? We'd need to make doctests aware of it.
  • have SR.var(...) as general symbol creation (which we already have).

There is some further rationalization around injection behaviour possible.

comment:55 Changed 4 years ago by dkrenn

I've announce this discussion on sage-devel https://groups.google.com/forum/#!topic/sage-devel/iy8Ck6BbhSE

comment:56 follow-up: Changed 4 years ago by was

I'm against deprecating var.

It is also common for a Python function to do something with side effects -- e.g., run a subprocess -- and also return some information about what it did, e.g., the exit code. This is computer programming, not mathematics.

I prefer

%var x, y

to

var x, y

by the way, since we have been generally deprecated non-% special commands. I was annoyed at first by, e.g., Jason Grout doing this, but I've come around.

comment:57 Changed 4 years ago by vbraun

os.system is a terrible example, its just a syscall wrapper. The subprocess module precisely tries to improve that interface by giving you separate check_output / check_call functions so you can create subprocesses in a more pythonic manner.

comment:58 in reply to: ↑ 56 Changed 4 years ago by nbruin

Replying to was:

It is also common for a Python function to do something with side effects -- e.g., run a subprocess -- and also return some information about what it did, e.g., the exit code. This is computer programming, not mathematics.

I thought something similar originally as well (although I thought "this is mathematics software, not computer programming"), but after seeing several questions from people getting thoroughly confused, I came to the conclusion that in this case having a side effect and a return value is a major source of confusion. See the original comment http://trac.sagemath.org/ticket/17447#comment:23. This is exacerbated by the fact that x=var('x') is very common in the documentation, which further trains people to be unaware of the side effect of var. See comment:32 for a hypothesis on why this happens (which argues why the return value of the side-effect-having var is a nuisance rather than helpful)

We really need to decide if deprecating the current behaviour of var is ever going to be doable (possibly with supporting indefinitely). If it's not we can stop right now: we'll just be adding extra interfaces which will only confuse people more. In that case it'll just be another victim of the tar-pits of interface compatibility (which does have value).

comment:59 Changed 4 years ago by bruno

  • Many examples given so far use sage: var('x') while x is the one variable that is injected automatically into the namespace. I feel like this particularity of x does not help an easy understanding of how it works for newcomers.
  • Many users may be satisfied if any non-yet-defined symbol was automatically injected into the namespace. I guess it would also help users to understand the difference between a "symbol" (from SR), on which nothing is known, and a "polynomial variable" (for instance from ZZ['x']). In some sense, this would imply that one can play around with symbolic variables, perform some simple calculations, etc. but that one should properly define their objects to obtain more functionalities and better performances.
  • Note that the previous point can be activated, in the Notebook only, using automatic_names(True). We may have a magic function to activate this behavior, as well as implicit_multiplication(True).
  • Amongst the different propositions for a new name if one is needed, I like the use of the keyword math that helps to makes the difference between a math symbol/variable/whatever and a python variable.
  • I also like the proposition to define %var x,y (or even %var x y). I think it would be nice to have something printed as for inject_variables() in this case, such as Defining x, y as symbolic variables.
  • For the deprecation, it is certainly less annoying for users if the change occurs with a new version number such as 7.0 or 8.0.

comment:60 follow-up: Changed 4 years ago by dimpase

I won't approve of declare_var, as it's too close to declare_war...

Anyhow, I don't understand what SR is. It looks as if it is no more than a hack provided by Sage, no more than that. Documentation says nothing about it. Perhaps before discussing var(), one should provide a definition. So far I don't understand the difference between symbolic variables and polynomial ring variables (except that the latter somehow don't work in solve()).

comment:61 in reply to: ↑ 60 ; follow-up: Changed 4 years ago by was

Replying to dimpase:

Anyhow, I don't understand what SR is. It looks as if it is no more than a hack provided by Sage, no more than that. Documentation says nothing about it. Perhaps before discussing var(), one should provide a definition. So far I don't understand the difference between symbolic variables and polynomial ring variables (except that the latter somehow don't work in solve()).

Calculus.

comment:62 follow-up: Changed 4 years ago by vbraun

To get this back on track, the minimal change that would satisfactorily resolve the issue would be a warning (but keep var indefinitely)

sage: var('x, y')    # warning but keep indefinitely
Warning: var has side effects. Consider using %var x, y
(x, y)

and

sage: %var x, y
Defining x, y as symbolic variables.

The latter would also have to work in doctests where percent-magics currently do not work.

comment:63 in reply to: ↑ 62 ; follow-up: Changed 4 years ago by nbruin

Replying to vbraun:

To get this back on track, the minimal change that would satisfactorily resolve the issue would be a warning (but keep var indefinitely)

sage: var('x, y')    # warning but keep indefinitely
Warning: var has side effects. Consider using %var x, y
(x, y)

and

sage: %var x, y
Defining x, y as symbolic variables.

I think this would be an improvement, so I'd be in favour if this change, even as proposed. Some details:

  • the warning correctly mentions a snag about var and then proposes an alternative that also has side-effects. I hate to make warning messages more than one line, but perhaps more information is beneficial here:
    Warning: var has side effects. Consider using SR.var('x,y') to return symbolic variables and %var x,y for binding them.
    
  • I personally like the printing of a message by %var for novices, but comment:32 suggests that the printing is a nuisance. So is it better for %var to do its work silently?

comment:64 in reply to: ↑ 63 ; follow-up: Changed 4 years ago by mmezzarobba

Replying to nbruin:

  • I personally like the printing of a message by %var for novices, but comment:32 suggests that the printing is a nuisance. So is it better for %var to do its work silently?

An option may be to print it using verbose(level=0), and document that the verbosity level may be set to negative to suppress such messages.

comment:65 in reply to: ↑ 61 ; follow-up: Changed 4 years ago by dimpase

Replying to was:

Replying to dimpase:

Anyhow, I don't understand what SR is. It looks as if it is no more than a hack provided by Sage, no more than that. Documentation says nothing about it. Perhaps before discussing var(), one should provide a definition. So far I don't understand the difference between symbolic variables and polynomial ring variables (except that the latter somehow don't work in solve()).

Calculus.

I don't think it's precise enough; for myself I understand SR as sequences of terms subject to certain rewriting rules, but neither what the term are, nor what the rewriting rules are, is not stated anywhere except in the source code and in examples...

comment:66 in reply to: ↑ 65 Changed 4 years ago by mmezzarobba

Replying to dimpase:

for myself I understand SR as sequences of terms subject to certain rewriting rules, but neither what the term are, nor what the rewriting rules are, is not stated anywhere except in the source code and in examples...

To repeat what I said on #15605, I for one basically view symbolic expressions as straight-line programs that are just required to evaluate to what you'd expect when you assign values to free variables. I believe this may be more accurate than thinking in terms of rewriting rules, since, as far as I know, nothing in the Sage implementation of symbolic expression systematically applies “rewriting rules”.

Many operations on symbolic expressions, however, only make sense with stronger assumptions on the expressions. Typically, simplifications are supposed to transform these ”programs“ into ”equivalent“ ones, but of course whether two ”programs“ are equivalent depends on what the variables can represent. In this context, the sensible thing to do IMO is to view all variables as complex by default, and require simplifications to be valid for arbitrary complex values of all variables (or more accurately, for a generic choice of complex values: for example, we probably do want x/x to simplify to 1). But of course this default does not cover all cases, for instance, expand() also makes sense for expressions containing constants from a finite field.

comment:67 in reply to: ↑ 64 Changed 4 years ago by nbruin

Replying to mmezzarobba:

An option may be to print it using verbose(level=0), and document that the verbosity level may be set to negative to suppress such messages.

I'm pretty sure that would be at least as annoying as dealing with values printed by a bare var (at least for doctests).

comment:68 follow-up: Changed 4 years ago by slabbe

Related to this discussion, note that there is another (strange) way to declare variables in Sage that currently works:

sage: ,var x a b c
(x, a, b, c)
sage: type(a)
<type 'sage.symbolic.expression.Expression'>

Apparently, it is ipython that provides this. I learn about the existence of this when I recently read the wikipedia page of Sage:

x, a, b, c = var('x, a, b, c')
# Note that IPython also supports a faster way to do this, by calling 
# this equivalent expression starting with a comma:
# ,var x a b c

comment:69 in reply to: ↑ 68 Changed 4 years ago by nbruin

Replying to slabbe:

Apparently, it is ipython that provides this. I learn about the existence of this when I recently read the wikipedia page of Sage:

x, a, b, c = var('x, a, b, c')
# Note that IPython also supports a faster way to do this, by calling 
# this equivalent expression starting with a comma:
# ,var x a b c

Yuck. It's a good illustration of the general confusion caused by the current behaviour of var. Reading the IPython documentation,

,var x a b c

is equivalent to

var("x","a","b","c")

which happens to do almost the same effect as the other var command given, except that the second form consists of a value returning expression and the first form is a non-expression statement that does not return a value (and hence prints nothing in the REPL).

Note: See TracTickets for help on using tickets.