Skip to content

Alternative assignment rework #1442

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Aug 11, 2025
Merged

Alternative assignment rework #1442

merged 16 commits into from
Aug 11, 2025

Conversation

mmatera
Copy link
Contributor

@mmatera mmatera commented Jul 31, 2025

This is an alternative, less disruptive implementation for the assignment refactor.

"lhs_ = rhs_"

def eval(self, expr, evaluation):
"Pattern[expr, _ = _]"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #1440 for how to do this without introducing Pattern[expr, ...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On further inspection, we don't use expr here at all. So the original seems better to me.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, let's return to the previous pattern.

@rocky
Copy link
Member

rocky commented Jul 31, 2025

I think this is much better.

I really like that we are starting to add tests for this. But more on that some other time.

I would like to spend some time going over this in more detail.

This is good and important work that you are doing in these PRs. Although we might not get everything right on the first round, I'd like to understand what's going on and make sure we don't have to do stuff that causes problems should we decide to revise.

@rocky
Copy link
Member

rocky commented Aug 1, 2025

This is an alternative, less disruptive implementation for the assignment refactor.

The "alternative" PR for this is #1441, right? So only one of these PR's would go through.

In what way is this "less disruptive" than #1441? If anything, I am seeing more code changes.

@mmatera
Copy link
Contributor Author

mmatera commented Aug 1, 2025

No, this is the assignment refactor based on #1441 instead of #1428. Is less disruptive because does not requires so many changes in mathics.core.builtins

@rocky
Copy link
Member

rocky commented Aug 1, 2025

No, this is the assignment refactor based on #1441 instead of #1428. Is less disruptive because does not requires so many changes in mathics.core.builtins

Ok, got it. I've suggested changes to #1441. Let's get them merged into master first.

Do you want to make the changes, or should I make changes and put in a PR to go into #1441?

) -> Optional[BaseElement]:
"lhs_ ^= rhs_"
def eval(self, expr: BaseElement, evaluation: Evaluation) -> Optional[BaseElement]:
"Pattern[expr, _ ^=_]"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've been thinking about the use of Pattern[expr] to create a placeholder for expr as opposed to using something like Expression() or to_expression() and I don't see using Pattern[] here as an improvement, even in those cases where get_eval_Expression() can't be used. (More on get_eval_Expression() below).

Previously, I mentioned the fact that now the docstring or pattern is a little more complicated and harder to understand the intent — capturing is put first and foremost, while everything else follows on the line.

It was claimed that this was somehow also faster. And here, I am not so sure...

First, upon loading, there is code to read the docstring and process it. This is up-front work, whether or not the builtin is ever used. Second, on each evaluation, there is code to bind this variable. So here the question is about whether that work is less than the work done by Expression or to_expression(). Well, if this is done only to provide a more complete error message, then one expects that most of the time and in those cases where it matters most, doing lazy expression creation is also a time win.

Of course, this concern to me is a secondary concern after the misdirection aspect of adding Patter[expr] in a docstring describing the matching pattern, which causes a function to get invoked.

Now let me come back to get_eval_Expression() and how that is different from Expression(). In PR #1446, I made a pass over the Mathics3 Python code to convert Expression() and to_expression() into get_eval_Expression(). Here is what I learned from this.

One difference that can occur in the form of the expression. get_eval_Expression() shows the expression closer to how it was entered, whereas using Expression(), you can tweak this however you want to. But typically, the expression will reflect the reordering of parameters and canonicalization.

This kind of thing is most apparent in algebraic expressions.

The other "difference" is that Cython can have trouble introspecting the call stack. I guess this has to do with its "optimization". Personally, I think we should ditch Cython run over Mathics3 modules, unconditionally. As best as I can tell, it is not giving us any speed advantage nowadays. As for using it as a type checker, I think nowadays the other type checkers, like Mypy, are much better.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've been thinking about the use of Pattern[expr] to create a placeholder for expr as opposed to using something like Expression() or to_expression() and I don't see using Pattern[] here as an improvement, even in those cases where get_eval_Expression() can't be used. (More on get_eval_Expression() below).

OK, in any case, it was not the main goal of this PR, but I found the chance to check if this work and see if there was some consensus to change in that direction.

Previously, I mentioned the fact that now the docstring or pattern is a little more complicated and harder to understand the intent — capturing is put first and foremost, while everything else follows on the line.

It was claimed that this was somehow also faster. And here, I am not so sure...

First, upon loading, there is code to read the docstring and process it. This is up-front work, whether or not the builtin is ever used.

OK, but this overhead happens at load time, and probably can be avoided if we compile and store the rules.

Second, on each evaluation, there is code to bind this variable. So here the question is about whether that work is less than the work done by Expression or to_expression(). Well, if this is done only to provide a more complete error message, then one expects that most of the time and in those cases where it matters most, doing lazy expression creation is also a time win.

Actually, what would be faster would be something like expr:Set[___], just because to match the pattern -when there are no other rules- would be faster than matching Set[x_,y_]. In any case, this would impact performance only if we use several assignment statements in a loop.

Of course, this concern to me is a secondary concern after the misdirection aspect of adding Patter[expr] in a docstring describing the matching pattern, which causes a function to get invoked.

Now let me come back to get_eval_Expression() and how that is different from Expression(). In PR #1446, I made a pass over the Mathics3 Python code to convert Expression() and to_expression() into get_eval_Expression(). Here is what I learned from this.

One difference that can occur in the form of the expression. get_eval_Expression() shows the expression closer to how it was entered, whereas using Expression(), you can tweak this however you want to. But typically, the expression will reflect the reordering of parameters and canonicalization.

An even simpler way to find the expression that is actually evaluated would be to store it in another attribute of the Evaluation object. This does not require any call to introspection functions or loops: just access to a Python variable. In any case, I think the proposal of using get_eval_Expression() is good in connection with the current development of the debugger API.

This kind of thing is most apparent in algebraic expressions.

The other "difference" is that Cython can have trouble introspecting the call stack. I guess this has to do with its "optimization". Personally, I think we should ditch Cython run over Mathics3 modules, unconditionally. As best as I can tell, it is not giving us any speed advantage nowadays. As for using it as a type checker, I think nowadays the other type checkers, like Mypy, are much better.

This is another problem. In any case, this is a different problem. By now, since the goal is to improve mathics.eval.assignment module, I removed the changes in mathics.builtin except for the ones required to pass the tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An even simpler way to find the expression that is actually evaluated would be to store it in another attribute of the Evaluation object. This does not require any call to introspection functions or loops: just access to a Python variable. In any case, I think the proposal of using get_eval_Expression() is good in connection with the current development of the debugger API.

See #1450 as an example of this approach

@@ -173,6 +173,11 @@ def match(self, expression: Expression, pattern_context: dict):
# yield new_vars_dict, rest
self.pattern.match(expression, pattern_context)

def get_sort_key(self, pattern_sort=True):
if not pattern_sort:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of parameter patten_sort, why not define function get_pattern_sort_key()?

It would be nice to add a docstring that explains that how each sort is used, e.g. get_sort_key is used in ordering of expression elements. While get_pattern_sort_key is used to prioritize which rule to select.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rocky
Copy link
Member

rocky commented Aug 4, 2025

I don't like the invented term "focus" to mean lhs without conditions, if that's what it is. Please change to something that conveys the meaning better. Thanks.

@mmatera
Copy link
Contributor Author

mmatera commented Aug 4, 2025

something

target? main_expression?

@rocky
Copy link
Member

rocky commented Aug 4, 2025

something

target? main_expression?

How about lhs_without_condition ?

@mmatera
Copy link
Contributor Author

mmatera commented Aug 4, 2025

something

target? main_expression?

How about lhs_without_condition ?

without condition, holdpattern, exception,... stripped_lhs? bare_lhs?

@rocky
Copy link
Member

rocky commented Aug 4, 2025

something

target? main_expression?

How about lhs_without_condition ?

without condition, holdpattern, exception,... stripped_lhs? bare_lhs?

I don't understand. Is it correct to say this is the left-hand side with Condition expressions removed?

If so, lhs_without_condition is reasonable. It has the "left-hand" part, "without" part and "condition" part in there and these are the important parts. If you want to use abbreviations for "condition", that is okay. lhs is already a common abbreviation that is used in the code.

(rms in GNU Emacs once used the French "sans" for "without"; it is has a slightly culinary meaning in English as well, but Spanish "sin" would be kind of funny appropriate; but it would be confusing too since "sin" is an English word.)

@mmatera
Copy link
Contributor Author

mmatera commented Aug 4, 2025

something

target? main_expression?

How about lhs_without_condition ?

without condition, holdpattern, exception,... stripped_lhs? bare_lhs?

I don't understand. Is it correct to say this is the left-hand side with Condition expressions removed?

If so, lhs_without_condition is reasonable. It has the "left-hand" part, "without" part and "condition" part in there and these are the important parts. If you want to use abbreviations for "condition", that is okay. lhs is already a common abbreviation that is used in the code.

(rms in GNU Emacs once used the French "sans" for "without"; it is has a slightly culinary meaning in English as well, but Spanish "sin" would be kind of funny appropriate; but it would be confusing too since "sin" is an English word.)

focus was introduced by Angus in the old Mathics code, meaning that is the part of the expression that defines which definition is going to be modified. I guess focus is in the sense of focal point in optics. This does not mean exactly to remove the wrapping Condition[...], but also other modifiers like HoldPattern or Except, or Pattern[...].
For example, if the lhs is
expr:HoldPattern[Condition[F[Q[x_],y],x>0]]
The assignment is done to a DownValue of F, or an UpValue of Q, and not on HoldPattern, depending on the kind of assignment (Set,TagSet, or UpSet).

This is why I think that names like lhs_keine_condition are not good names.

@rocky
Copy link
Member

rocky commented Aug 4, 2025

something

target? main_expression?

How about lhs_without_condition ?

without condition, holdpattern, exception,... stripped_lhs? bare_lhs?

I don't understand. Is it correct to say this is the left-hand side with Condition expressions removed?
If so, lhs_without_condition is reasonable. It has the "left-hand" part, "without" part and "condition" part in there and these are the important parts. If you want to use abbreviations for "condition", that is okay. lhs is already a common abbreviation that is used in the code.
(rms in GNU Emacs once used the French "sans" for "without"; it is has a slightly culinary meaning in English as well, but Spanish "sin" would be kind of funny appropriate; but it would be confusing too since "sin" is an English word.)

focus was introduced by Angus in the old Mathics code, meaning that is the part of the expression that defines which definition is going to be modified.

The common programming language term for this is: reference, and here, left-hand-side reference.

I guess focus is in the sense of focal point in optics.

The problem with "focus" as used here is that there are numerous ways this word could be interpreted and used as an analogy. Like starting with whether the meaning is used as a noun or a verb.

In places where you want to appropriate a term from one domain to another, the term should have a very clear, specific, and unambiguous meaning. For example, when Robert Tarjan took the term "amortized" from finance and applied it to algorithms, it is very clear what that means.

This does not mean exactly to remove the wrapping Condition[...], but also other modifiers like HoldPattern or Except, or Pattern[...]. For example, if the lhs is expr:HoldPattern[Condition[F[Q[x_],y],x>0]] The assignment is done to a DownValue of F, or an UpValue of Q, and not on HoldPattern, depending on the kind of assignment (Set,TagSet, or UpSet).

This is why I think that names like lhs_keine_condition are not good names.

Okay, fair enough. How about lhs_reference ?

@mmatera
Copy link
Contributor Author

mmatera commented Aug 4, 2025

lhs_reference

lhs_reference sounds good.

@rocky
Copy link
Member

rocky commented Aug 4, 2025

lhs_reference

lhs_reference sounds good.

Thanks for your patience and tolerance.

@mmatera
Copy link
Contributor Author

mmatera commented Aug 5, 2025

lhs_reference

lhs_reference sounds good.

Thanks for your patience and tolerance.

Sure. Thank you for your careful revision!

Copy link
Member

@rocky rocky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM - but see suggested comment changes.

@mmatera
Copy link
Contributor Author

mmatera commented Aug 11, 2025

OK, let's go on with this, and iterate

@mmatera mmatera merged commit 32e0747 into master Aug 11, 2025
14 checks passed
@mmatera mmatera deleted the alternative_assignment_rework branch August 11, 2025 12:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants