Skip to content

Expression refactor#173

Merged
rocky merged 8 commits intomasterfrom
expression-refactor
Feb 24, 2022
Merged

Expression refactor#173
rocky merged 8 commits intomasterfrom
expression-refactor

Conversation

@rocky
Copy link
Copy Markdown
Member

@rocky rocky commented Feb 24, 2022

No description provided.

Remove class fields that mirror instance fields
remove evaluate from BaseExpression
evaluate() in Number is covered by evaluate in Atom
Remove the check_stopped() test. If needed either another
function will be added and/or it will be covered by a higher level.
before removing a number of them.
@rocky rocky marked this pull request as draft February 24, 2022 07:21
Comment thread mathics/core/symbols.py
create_expression: Any

# __new__ seems to be used because this object references itself.
# In particular:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@rocky, Notice that some descendants of BaseExpression decide the class of the object according to the input. For example, Real receives a float or a string and a precision parameter. According to the parameter, a MachineReal or a PrecisionReal object is created. In the case of Symbol, we use __new__ to return always the same instance of symbols with the same name.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@mmatera I had considered that, but experiments show that this might not be the case.

Consider this code:

class Foo:
    def __new__(cls, *args, **kwargs):
        self = object.__new__(cls)
        print("XXX", id(self))
        return self


x = Foo()
y = Foo()

print(id(x))
print(id(y))
print(x == y)

If you run it you will see that x and y have different memory locations. And that x is not the same as y.

So what leads you to believe that "new" is doing this?

Also, I should say that if you wanted a single memory instance there is a design pattern to do this, but this isn't the way.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@rocky, no: I am talking about Symbol. I just mention this because I found that sometimes problems appear if you redefine __new__ in a subclass with a different pattern of arguments than the default __new__ in the parent class has.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

A link to a line(s) of code would be nice.

Copy link
Copy Markdown
Member Author

@rocky rocky Feb 24, 2022

Choose a reason for hiding this comment

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

Also, when there is non-obvious stuff going on, comments in code are a good place to describe this. If that gets too long an involved, we also have a developer's guide.

Github comments in PRs will probably get lost.

Think of coding like doing school homework in certain classes: you don't get credit for getting the right answer, you also need to show your work and calculations.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

One other thing. I am drafting a discussion for this whole Expression mess. So let's hold off on detailed discussions until I post that, and we can carry on the discussions there.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Here is my [small] opinion: maybe making every expression have a different memory allocation is good (makes the things "easier" in other places). But Python has exited in the last decades, if they have chosen to do it this way, probably there is a why (not saying that Python makes the best design choices, it doesn't). So we should (I'm not asking anyone to do this, just saying my opinion) do it the Pythonic way.

@mmatera
Copy link
Copy Markdown
Contributor

mmatera commented Feb 24, 2022

def __new__(cls, name, sympy_dummy=None):

def __new__(cls, value, p=None) -> "Real":

I do not have the code that produces the error (was a failed experiment), but probably you will detect it faster than I if it comes up.

@rocky
Copy link
Copy Markdown
Member Author

rocky commented Feb 24, 2022

def __new__(cls, name, sympy_dummy=None):

def __new__(cls, value, p=None) -> "Real":

I do not have the code that produces the error (was a failed experiment), but probably you will detect it faster than I if it comes up.

Good - thanks. I will document this weird behavior then in this branch and update the comment.

Comment thread mathics/core/symbols.py
self._cache = None
return self

def clear_cache(self):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why is useful to sort the method by alphabetic order?

Copy link
Copy Markdown
Member Author

@rocky rocky Feb 24, 2022

Choose a reason for hiding this comment

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

When we have very long lists of things it helps me to follow things. Suppose I want to list the 8 juices in V8 juice. I find I can do that more easily if I give the items in alphabetic order.

I know one can always search for stuff, but I'll just tend to scroll when things are in alphabetic order and am pleased when it appears where I expect it. (In the process having passed the other names may be a win too since it reminds me of what's there.)

There are a couple of caveats though. Some methods and definitions need to be in a certain order. And so when the default is alphabetic, the break in order suggests that this kind of thing is going on.

Also, when there is small list of methods, ordering by logical group is okay.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Here I agree, that is why all code editors (which I know) sort the files alphabetically.

(This may not be necessary after drying BaseElement, but as we [I] already saw in the past, it is better to do the things step by step)

Copy link
Copy Markdown
Contributor

@TiagoCavalcante TiagoCavalcante left a comment

Choose a reason for hiding this comment

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

My take would be to merge this and continue in other PRs.

This Looks Great For Me.

Comment thread mathics/builtin/numbers/constants.py Outdated


class _NumpyConstant(_Constant_Common):
class _NumpyConstant(_Constant_Common, NumericOperators):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Just a thing that came into my mind now: shouldn't _NumpyConstant be a subclass of Number?

Copy link
Copy Markdown
Member Author

@rocky rocky Feb 24, 2022

Choose a reason for hiding this comment

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

I don't know but I will check and let you know...

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This *Constant are superclasses of some Builtins. Builtins are not Elements so, I think

  • They shouldn't be a subclass of Number that are Elements.
  • In general, subclasses of Builtin shouldn't implement NumericOperators.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@mmatera, I don't understand this enough to give my opinion about the first, but I agree with the second.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@mmatera Ok, it took me a while to understand what you mean, but yes, I believe you are correct and 1edd13d should address this.

@TiagoCavalcante In effect what mmatera is saying is that inside Python you can't write say Pi() + 3 (with or without the parenthesis) like you could do say Integer(5) + 3. This is because Pi acts as a built-in symbol which needs to be evaluated. You could presumably write in some code to do the evaluation, e.g. call the evaluate() function, but the the whole convenience of using infix + is kind of lost.

So in summary, this kind of shorthand just doesn't apply for Mathics built-in variables/symbols like this.

Comment thread mathics/core/symbols.py
finally:
evaluation.dec_recursion_depth()

def equal2(self, rhs: Any) -> Optional[bool]:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think this is kind of unnecessary, as we have sameQ, maybe the == part can be moved to there.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

As far as I can tell SameQ and Equal are different, both operators are pervasive over all kinds of expression types. And the note that the specific implementation here is not the same as SameQ.

That said, possibly it could be attached in some other class and just be made something that is mandatory to define.

Your thoughts @mmatera ?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

SameQ is something that all elements should handle. equal2 I do not think so, since in general require an Evaluation object to be determined.

Comment thread mathics/core/symbols.py
return self

def flatten_pattern_sequence(self, evaluation) -> "BaseExpression":
return self
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

These functions in this class don't smell quite right for me.

Copy link
Copy Markdown
Member Author

@rocky rocky Feb 24, 2022

Choose a reason for hiding this comment

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

I agree, I think it should be moved to Expression. Your thoughts @mmatera ?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yep, al these flatten_* should be specific for Expression, not for general elements

Comment thread mathics/core/symbols.py
return []

return item_is_free(self, form, evaluation)
def get_attributes(self, definitions):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Here and in other places I think that @attribute should be used.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I sort of agree but this is a pervasive change and not something to be put into this PR now.

I had also considered this for get_head and get_elements.

I have opened #174 to track this.
Feel free to add to that or correct what is there.

Comment thread mathics/core/symbols.py

def is_inexact(self) -> bool:
return self.get_precision() is not None
# Probably, this method shouldn't be here.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think so, numbers don't have elements.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Noted in #174 for another PR

Comment thread mathics/core/symbols.py
# Compatibily with old code. Deprecated, but remove after a little bit.
get_leaves = get_elements

def get_float_value(self, permit_complex=False):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Maybe it also shouldn't be here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Noted in #174 for another PR.

Comment thread mathics/core/symbols.py
def get_int_value(self):
return None

def get_lookup_name(self):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This function also doesn't smell quite right for me.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I am guessing this should be an Expression thing, not something here. @mmatera ?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Probably this is something that you want to keep since you call it over the elements of an expression to determine the upvalue rules.

Comment thread mathics/core/symbols.py


# FIXME: move to new module element.py
class NumericOperators:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I like this, as a first step. However, notice that Plus[3,"a"] or Times[x,Graphics[{}]] are perfectly valid Expression s in WL.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nevertheless, from the Python side, maybe it also makes sense to define specialized behaviors for expressions like

Integer(1)+Integer(2)

or

Integer(3)+MachineReal(.12)

Copy link
Copy Markdown
Member Author

@rocky rocky Feb 24, 2022

Choose a reason for hiding this comment

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

The issue here is more about what we need to express in the Python code. While I can sort of see and tolerate writing
abs(Integer(-8)) I am not convinced it is a good idea over writing Expression(SymbolAbs, -8).

And I would not be surprised if down the line we come up with a better way of doing such things.

So the question is whether in mathics Python code we would ever want to write Integer(3) + String("a") or the other thing.

My take is that when the need arises we can reconsider this. But I really doubt the right thing will ever be to ever put those operators in the base class.

And right now clarity and simplicity is way more important that being able to handle hypothetical edge cases like the ones described above.

Sure, inside Mathics, you should be able to do that. Inside Python code implementing Mathics, I'd need convincing though.

Copy link
Copy Markdown
Member Author

@rocky rocky Feb 24, 2022

Choose a reason for hiding this comment

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

Nevertheless, from the Python side, maybe it also makes sense to define specialized behaviors for expressions like

Integer(1)+Integer(2)

or

Integer(3)+MachineReal(.12)

Ok. It is not clear that this is broken in the current code. None of the tests show it. If this isn't used right now in the code base let's leave it for later or feel free to add the mixin in the right place.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

My point is: in the Python side, usually you do not need to write abs(Integer(-8)) but something like

def apply(expr, evaluation):
    '''F[expr_]'''
     ....
     new_expr = abs(expr)

in a way that element eventually is Integer(-8) or Symbol("Pi") or Expression("F",37) or "Hello world". With this implementation, the last option would raise an exception. But OK, probably you would need to check that expr is something that makes sense to use as an argument of abs.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nevertheless, from the Python side, maybe it also makes sense to define specialized behaviors for expressions like

Integer(1)+Integer(2)

or

Integer(3)+MachineReal(.12)

Ok. It is not clear that this is broken in the current code. None of the tests show it. If this isn't used right now in the code base let's leave it for later or feel free to add the mixin in the right place.

No, I do not mean that your changes break something. I'm OK with them. What I am talking about is just to consider possible extensions, just to avoid repeating mistakes. I mean, maybe in the future we realize that makes sense to specialize add for integers, in a way that instead of return an expression Expression("Plus",Integer(1),Integer(2)) returns directly Integer(3) (for example).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yeah, let's revisit this later.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

in a way that element eventually is Integer(-8) or Symbol("Pi") or Expression("F",37) or "Hello world". With this implementation, the last option would raise an exception. But OK, probably you would need to check that expr is something that makes sense to use as an argument of abs.

Yes, I'd rather raise an exception here than east and execute on some mystery meat that might come back. (A friend of mine asked a Vietnamese person if they really ate dogs in Viet Nam; the response was "in the North only").

Comment thread mathics/builtin/numbers/calculus.py Outdated


class Integers(Builtin):
class Integers(Builtin, NumericOperators):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Again, a Builtin shouldn't be at the same time NumericOperators

@mmatera
Copy link
Copy Markdown
Contributor

mmatera commented Feb 24, 2022

If we keep from here just the changes in the core side and revert the changes in builtins, I would merge it now.

@rocky rocky force-pushed the expression-refactor branch from 1edd13d to 078b0bb Compare February 24, 2022 17:32
@rocky rocky marked this pull request as ready for review February 24, 2022 17:38
Copy link
Copy Markdown
Contributor

@mmatera mmatera left a comment

Choose a reason for hiding this comment

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

LGTM

Copy link
Copy Markdown
Contributor

@TiagoCavalcante TiagoCavalcante left a comment

Choose a reason for hiding this comment

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

LGTM

@rocky rocky merged commit c75cc99 into master Feb 24, 2022
@rocky rocky deleted the expression-refactor branch March 12, 2022 21:50
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.

3 participants