Support splats on left-hand sides of multiple assignment expressions#10410
Conversation
|
What does the community think of us having something like https://rubyheroes.com/ but for Crystal? I might have a nominee or two in my head... |
|
Thinking that if the splat variable happens to be the underscore then we should omit that assignment altogether to avoid creating a temporary copy, since that range index can be fairly large. |
|
If this PR is merged, it opens an easy migration path from the following: a, b = [1, 2, 3, 4, 5] # Silently ignores the last 3 itemsto a, b, *_ = [1, 2, 3, 4, 5] # Exactly the same but explicit-- this is good syntax to use if anyone is actually relying on the ignoring behavior, rather than having it by accident. What I'm leading at, and the reason why there would need to be a "migration path" for anything, is that this silent ignoring behavior is very unexpected in my opinion, and should be replaced with an error about size mismatch (one like you see at the top of this PR). That is, a size check should be added to it. You can say what you want about Ruby also ignoring this,... but somehow the error conditions in the new situations that this PR introduces make sense, don't they? Well, Ruby also silently ignores all of those cases, even up to Additionally, this pull request literally makes those two syntaxes (see code blocks above) aliases of each other. Also I don't know any other programming language that ignores Instead of gaining aliases I propose to use the pre-existing With this in mind, it's sad to see the [post-1.0] label attached. Without this migration path we can't easily deprecate the error silencing, and after 1.0 we can't deprecate it at all. |
|
I agree with @oprypin : it would be nice if I would like to think that if someone uses |
|
I'm thinking that if we really wanted to avoid a breaking change in some way, we could have a flag that says that Though maybe it's a lot of work... |
|
The other day I thought about this and believe that maybe Crystal doesn't need 1-to-n multiple assignment after all if it leads to confusion between Ruby's lax multiple assignment rules (dropping elements at the end, including with block parameters) and pattern matching semantics (count mismatches, with or without LHS splats). Maybe it's time we have general pattern matching... (We could still define 1-to-n multiple assignment in terms of some pattern matching construct afterwards.) |
|
@HertzDevil I don't see how that comment is constructive here. Maybe you changed your mind at a whim, but there's nothing wrong with this pull request in that light. Let's not match Ruby, let's match Python 🙃
I have already outlined in my comment exactly how to remove the confusion. |
|
@HertzDevil Is this pull request in a reviewable state? Or, why is it a draft? The rest I said in #11145 (comment) |
oprypin
left a comment
There was a problem hiding this comment.
Wait, that's quite a lot of CI failures. And the spec failures reproduce locally. bin/crystal spec/compiler/codegen/multi_assign_spec.cr
|
Thank you @HertzDevil ! |
|
Just to make sure again, should we require the value on the right-hand side of 1-to-n assignments to be value = {0 => "x", 1 => "y", 2 => "z"}
# existing 1-to-n assignments (in a separate PR)
a, b, c = value
# 1-to-n splat assignments
# ill-formed because of `b = value[1..-2]`
a, *b, c = value
# 1-to-n splat assignments
# raises because of `b = value[-2]` and `c = value[-1]`
*_, b, c = value
# okay???
a, *b, c = Hash(Int32 | Range(Int32, Int32), String).new { |hsh, key| hsh[key] = 0 }Related: #10937 (comment) |
|
I'd say a restriction to |
|
We could consider adding |
|
This is probably a topic for a follow-up discussion, though. I'd prefer to merge this as is. There was no type restriction prior to this change. We can discuss and add that afterwards (just need to make sure it lands before 1.3 if it's a breaking change on newly-introduced behaviour). |
|
I extracted the discussion about |
Implements #132 by supporting one level of decomposition on the left-hand sides of multiple assignment expressions. The first case is when the right-hand side consists of a single expression:
__temp_1must respond to#size,#[](Int), and#[](Range). This includesArray,BitArray,Slice,String,Tuple, and to some extentRegex::MatchData. The main obstacle was gettingTupleto work here without allocating anArray, but now that #10379 is merged, the following is now possible:The second case is when there are multiple expressions on the RHS:
No temporaries are created, and it is a compile-time error to specify more non-splat assignment targets than RHS expressions (the shortest code that could trigger this is
a, b, *c, d = 1, 2).As with the splat-less case, each assignment target, including the splat target, can be a variable, a setter call, a
[]=call, or the underscore. The underscore can be used to omit some of the RHS values, since writing to it has no effect:Like other places that perform splat collection, using more than one splat on the LHS is an error.