Skip to content
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

Add augmented assignment operators to Macaulay2 language #3079

Merged
merged 22 commits into from
Jan 24, 2024

Conversation

d-torrance
Copy link
Member

For most flexible binary operators in Macaulay2, we add a corresponding augmented assignment operator. (This is something that would be very useful if/when we add atomic integers at top-level -- see #3044.)

Here's the proposed documentation for more information:

i1 : help "augmented assignment"

o1 = augmented assignment
     ********************

     Most binary "operators" have an associated augmented assignment (see
     https://en.wikipedia.org/wiki/augmented_assignment ) operator that modifies
     the given object using the corresponding binary operator.  Essentially, x
     OP= y is equivalent to x = x OP y.

     +---------------+
     |  i1 : x = 2   |
     |               |
     |  o1 = 2       |
     +---------------+
     |  i2 : x += 3  |
     |               |
     |  o2 = 5       |
     +---------------+
     |  i3 : x -= 4  |
     |               |
     |  o3 = 1       |
     +---------------+
     |  i4 : x *= 5  |
     |               |
     |  o4 = 5       |
     +---------------+
     |  i5 : x /= 6  |
     |               |
     |       5       |
     |  o5 = -       |
     |       6       |
     |               |
     |  o5 : QQ      |
     +---------------+

     The following augmented assignment operators are supported.

     +---------------------------------------------------------------------------------+
     |  i6 : importFrom(Core, "augmentedAssignmentOperators");                         |
     +---------------------------------------------------------------------------------+
     |  i7 : augmentedAssignmentOperators                                              |
     |                                                                                 |
     |  o7 = (<<=, >>=, ||=, <==>=, |-=, ==>=, ===>=, |=, ^^=, &=, ..=, ..<=, -=, +=,  |
     |       ------------------------------------------------------------------------  |
     |       ++=, **=, *=, \\=, /=, \=, %=, //=, @=, @@=, ^=, _=, ^**=)                |
     |                                                                                 |
     |  o7 : Sequence                                                                  |
     +---------------------------------------------------------------------------------+

     See also
     ========

       * "installing augmented assignment methods"

i2 :        * "installing augmented assignment methods"

o2 = installing augmented assignment methods
     ***************************************

     In most cases, the default behavior of "augmented assignment" gives the
     desired result.  But in some situations, it may be useful to override this
     behavior and install a custom method for a given type.

     Consider the following example.

     +-------------------------------------------------------+
     |  i1 : Foo = new SelfInitializingType of MutableList;  |
     +-------------------------------------------------------+
     |  i2 : net Foo := x -> net x#0;                        |
     +-------------------------------------------------------+
     |  i3 : Foo + Foo := (x, y) -> Foo {x#0 + y#0};         |
     +-------------------------------------------------------+
     |  i4 : x = Foo {1}                                     |
     |                                                       |
     |  o4 = 1                                               |
     |                                                       |
     |  o4 : Foo                                             |
     +-------------------------------------------------------+
     |  i5 : y = Foo {2}                                     |
     |                                                       |
     |  o5 = 2                                               |
     |                                                       |
     |  o5 : Foo                                             |
     +-------------------------------------------------------+
     |  i6 : x += y                                          |
     |                                                       |
     |  o6 = 3                                               |
     |                                                       |
     |  o6 : Foo                                             |
     +-------------------------------------------------------+

     Note that an intermediate Foo object was created and then assigned to x.
     Instead, it would be more efficient if x was modified directly.

     The first two lines below do exactly the same thing; the second line is
     syntactic sugar for the first.

     +------------------------------------------------------------------+
     |  i7 : installMethod(symbol +=, Foo, (x, y) -> (x#0 += y#0; x));  |
     +------------------------------------------------------------------+
     |  i8 : Foo += (x, y) -> (x#0 += y#0; x);                          |
     +------------------------------------------------------------------+
     |  i9 : x += y                                                     |
     |                                                                  |
     |  o9 = 5                                                          |
     |                                                                  |
     |  o9 : Foo                                                        |
     +------------------------------------------------------------------+

     In some cases, it may be useful to fall back on the default behavior of the
     given operator.  When this is desired, the installed method should return
     the "Default" symbol.

     +-------------------------------------------------------+
     |  i10 : Bar = new SelfInitializingType of List;        |
     +-------------------------------------------------------+
     |  i11 : net Bar := x -> net x#0#0;                     |
     +-------------------------------------------------------+
     |  i12 : Bar * Bar := (x, y) -> Bar {{x#0#0 * y#0#0}};  |
     +-------------------------------------------------------+
     |  i13 : Bar *= (x, y) -> if isMutable x#0 then (       |
     |            print "using custom method";               |
     |            x#0#0 *= y#0#0; x) else Default;           |
     +-------------------------------------------------------+
     |  i14 : x = Bar {new MutableList from {3}}             |
     |                                                       |
     |  o14 = 3                                              |
     |                                                       |
     |  o14 : Bar                                            |
     +-------------------------------------------------------+
     |  i15 : y = Bar {{4}}                                  |
     |                                                       |
     |  o15 = 4                                              |
     |                                                       |
     |  o15 : Bar                                            |
     +-------------------------------------------------------+
     |  i16 : x *= y                                         |
     |  using custom method                                  |
     |                                                       |
     |  o16 = 12                                             |
     |                                                       |
     |  o16 : Bar                                            |
     +-------------------------------------------------------+
     |  i17 : y *= x                                         |
     |                                                       |
     |  o17 = 48                                             |
     |                                                       |
     |  o17 : Bar                                            |
     +-------------------------------------------------------+

     See also
     ========

       * "augmented assignment"
       * "installing methods"

Note that not every flexible binary operator is supported, e.g., it wouldn't make sense to have an augmented assignment operator for == since === is already in use.

As proof of concept, we update a few places in Core with i = i + 1 to use i += 1 and also add augmented operator support to the Python package.

One for most flexible binary operators (omitting ones that would
introduce ambiguity or give syntax errors) with the same precedence as
= (to match C).
This way it won't complain when we try to install a function with two
arguments.
They're binary and they're flexible, but they're a little different
than the members of flexibleBinaryOperators.  In particular, we don't
want to run installAssignmentMethod on them, which is why we keep them
separate from flexibleBinaryOperators.
If the method returns the "Default" symbol, then fall back to the
usual behavior.

This is useful for example for the Python package, where we'd like +=
to call the __iadd__ method, but only if it exists.
This will be used by augmented assignment.  The problem was that we
first want to check if a user-defined method for augmented assignment
is installed, which requires evaluating the code on the left-hand side
of the operator.  But if we *don't* use a user-defined method (which
will be the case most of the time), then we want the original code,
since that's what the binary operators expect.

The solution is to create a member of the Code union that saves the
evaluated code.  Then we can evaluate early on but still call the
binary operators later if need.
Evaluate everything immediately but store it as evaluatedCode for
later.
@mahrud
Copy link
Member

mahrud commented Jan 24, 2024

By the way, have you seen #1596?

@d-torrance
Copy link
Member Author

By the way, have you seen #1596?

Not until you pointed it out, but I like the idea a lot! The implementation would be pretty similar to this PR, I think.

@mahrud
Copy link
Member

mahrud commented Jan 24, 2024

Yeah, I figured it might be easy to do if you're already familiar with that part of the code.

@DanGrayson DanGrayson merged commit e2f580c into Macaulay2:development Jan 24, 2024
6 checks passed
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