-
Notifications
You must be signed in to change notification settings - Fork 160
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
Syntax extension: list unpacking resp. destructuring #40
Comments
This is a bit tricky because we don't really have (internally in the parser) a proper notion of an "lvalue" (somewhere that an assignment can store something) or of an "l-expression" essentially an expression that evaluates to an l-value. As a result it is hard to store the multiple left-hand sides of the assignment after you parse them and before you get to the RHS. Obviously it could be done, but it would not be a small change. |
@ChrisJefferson this is one of the issues we discussed earlier today |
To mention some of the things discussed on #457: The only use case I care about, and which I think is useful in many places, is the one were the left side is a list of variable names, i.e. x,y,z := [1,2,3];
q,r := QuotientRemainder(34,3); It was pointed out on #457 that dealing with e.g. the following would be tricky, and my initial reaction to this is "well, then let's not support it" y := [4,5,6];
i := 2;
x := y;
x, i, x[i] := [[1,2,3],1,4]; Of course there still might some usecases of this kind people might like to have, e.g. to assign to records: res:=rec();
res.q, res.r := QuotientRemainder(34,3); But (a) if implementing this is hard, I don't feel bad not supporting it (we are not taking away anything from anybody, after all), and (b) of course we can still think about the possibility to relax the restriction to "pure list of variables names on the left hand side" a bit in the future; if we can come up with clear and somewhat intuitive rules for that, which also can be implemented with sane effort, sure, fine, let's do it; but let's not prevent ourselves from implementing a useful extension just because there might be an extension of the extension that we can't bother to deal with... One minor extension that might be worth discussing is whether there should be a way to "capture the rest" of a list... I.e. one may want to capture the first two entries, and ignore the rest, or assign it all to a new list. Say, if there was a new function R, x, y := PolynomialRingWithIndeterminates(Rationals, ["x", "y"]); But sometimes I may not care about the indeterminates individually, then it would be handy if I could write, say R, *indets := PolynomialRingWithIndeterminates(Rationals, 10); to get all 10 indeterminates into This is similar to a feature in Python 3, see https://www.python.org/dev/peps/pep-3132/ (theirs is more generic, though) |
Regarding "capturing the rest of the list", maybe this syntax would fit better, as it imitates our vararg syntax: R, indets... := PolynomialRingWithIndeterminates(Rationals, 10); |
Just a basic comma seperated list |
The notation for |
Should it, though? In function varargs and also here, Whereas #2043 is more about a "dual" use case: expanding a list resp interpolation. And unlike the examples here, this interpolation can be used multiple times, eg So having very similar syntax for these two related but different concepts is IMHO not necessarily a win... |
That's a reasonable argument! To be honest, having thought further, I'm happy with either notation. |
Well, just to clarify, I am also not opposed to using Or perhaps this whole idea should first be brought up on the gap@ list to see what people think. |
I want this feature to work, but I remain a bit concerned that we will end up with lots of bug reports of the form "Why doesn't So that opens up all the problems I raised long ago about order of evaluation of LHSs and assignment to them. Even if we could agree a precise and reasonably intuitive solution to that, it would require a fairly major rewrite of the interpreter to fix because we don't really have a notion of lvalue, but we also don't want to do much lookahead. I can buy the theory that we acknowledge these problems and just stick to the simplest possible case (a list of plain variables) although even then, if one or more of them is an AutoGVar (or a thread-local variable with an initializer) then we have to be clear WHEN the side effects happen (before or after evaluating the RHS, for instance). However, in this case, I think we should keep it as simple as possible, which speaks against any notation for "capturing the remaining arguments". There is also the question of what is on the other side. If Another way to deal with that one, and maybe also make some things a bit cleaner would be I'm not trying to be awkward, but for something as fundamental as assignment I think it's worth trying to find the "right" answer. |
I am glad to get feedback and to discuss this extensively, nothing awkward about it :-). I appreciate it, thank you for taking the time! I find the idea of allowing
But it is seems to me to be somewhat orthogonal to (a) all the problems you point out, and (b) the desire to have list destructuring or unpacking, like in Python 3, where I can also do things like this:
Regarding the "untidy" difference between As to people asking "Why doesn't x[1],y[2] := QuotientRemainder(...) work?" I'd have no qualms to answer that with "Because there are difficult semantical issues with that, which we so far had no time to resolve. For details, please refer to URL.". Also, it would be fairly easy to document: The LHS can either be a simple sequence This leaves the question about auto vars (and thread locals with initializers); I am not afraid of dealing with them, but indeed, we should be very careful to design the semantics so that a future extension (where the LHS can be more general) is not hampered by having to deal with extending "old-style semantics" in an unnatural or super complicated way (i.e.: if we now said "first the RHS is evaluated, then the LHS", and later we want or need to do it differently in at least some cases, that'd be super awkward). So, yeah, by all means, let's think about it, and see if we can come up with a plan that we consider future proof (whether we support selectors in the LHS var list at first or not is IMHO secondary). I'll start by pointing out that right now, GAP evaluates the LHS first, then the RHS, simply because we are eager. That is a bit unfortunate, IMHO, as it doesn't match what many other languages do, and it indeed makes it much harder to extend things (this is also the reason we never implemented in-place modification operators like To wit:
In contrast, we get this in Python 3:
Trying to assign to
Also interesting is to see what Python does here when using
So Python really treats this as In particular, Python first evaluates the RHS, before it starts to evaluate the LHS. Which is what I'd like for GAP, except it's of course far too late for that, I am afraid: we eagerly evaluate the LHS first, then the RHS, in the order we read them. Getting back to GAP: What should happen if
Regarding AutoGVar: I'd try to emulate whatever happens now. If (Doing that of course means we need to somehow track the LHS references, but having stared extensively at the code for a few days, I think it could be done with moderate effort... of course the devil is in the details, so in the end, we'll just have to try it and risk failure, but I think even failure would teach us valuable things). Anyway, perhaps with these ideas, we can also revisit the idea of implementing If the above idea for semantics does not sounds to non-sensical and impossible, and if there is some general interest in at least considering this feature for inclusion (of course assuming the implementation has sane semantics etc. etc.), then I would dare trying to work on a prototype, with which one could hopefully discuss this further and find new obstacles etc. ;-). Finally: I am not quite sure what the syntax |
Regarding
to swap the content of
would be equivalent to I am a bit unhappy about the behavior of this last example; e.g. for us Germans, we use decimal commas, so it is quite conceivable that somebody mistypes Another question: If we were to implement this, should we also allow the following? (How does Magma handle this?)
|
Python (and various other languages) has a nice feature for unpacking a list into multple variables:
a,b,c := [1,2,3];
assigns the values 1,2,3 to the variables a,b,c. This is quite useful for functions returning multiple values as tuples. Say you have a function which returns two values in a list. Then often, you might write code like this:
which you can turn into this with list unpacking:
There are various pitfalls with that, though, once you go beyond plain identifiers on the left side. But I think python already thought about most (?) of those. Here are some example of a Python session (note that Python indexes starting from 0):
But I am not sure whether we can "easily" support that in our parser...?
The text was updated successfully, but these errors were encountered: