Feature #8895

Destructuring Assignment for Hash

Added by Jack Chen 7 months ago. Updated 4 days ago.

[ruby-core:57131]
Status:Open
Priority:Normal
Assignee:-
Category:-
Target version:-

Description

=begin
Given Ruby already supports destructuring assignment with Array (a, b = [1, 2]), I propose destructuring assignments for Hash.

== Basic example

params = {name: "John Smith", age: 42}
{name: name, age: age} = params

# name == "John Smith"
# age == 42

This would replace a common pattern of assigning hash values to local variables to work with.

== General syntax

{ => , … } =

# Symbols
{ foo: bar } = { foo: "bar" }
bar == "bar"

# Potential shorthand
{ foo } = { foo: "bar" }
foo == "bar"

== Use cases:

# MatchData
{ username: username, age: age } = "user:jsmith age:42".match(/user:(?\w+) age:(?\d+)/)
username == "jsmith"
age == "42"

== Edge cases

# Variable being assigned to more than once should use the last one
{ foo: var, bar: var } = {foo: 1, bar: 2}
var == 2

Thoughts?
=end

History

#1 Updated by Peter Zotov 7 months ago

This is an awesome idea! However, the parser bit is really evil. I tried implementing it myself (quite a bit of time ago) and completely gave up. It's not above my comprehension, but the amount of work even for my Ruby parser port is huge and daunting. Doing it in C is a nightmare.

That being said, I'm willing to discuss and/or provide guidance to any interested parties.

#2 Updated by Marc-Andre Lafortune 7 months ago

I suggested something similar in .
Here is a summary from my similar suggestion made in :

{key: 'default', otherkey:, **otheroptions} = {otherkey: 42, foo: 'bar'}
key # => 'default'
other
key # => 42
other_options # => {foo: 'bar'}

You'll note that it doesn't give the possibility to map the key to a different variable. Indeed, I don't think that it would be useful and I would rather encourage rubyists to use meanginful option and variable names. It also makes very similar to the way we declare keyword arguments for methods, so no additional learning curve. Your proposal is quite different.

#3 Updated by Tsuyoshi Sawada 7 months ago

=begin
Given that destructive assignments with array prohibits the [ ] on the left side of the assignment, that is:

a, b = [1, 2]

instead of:

[a, b] = [1, 2]

it would be more consistent if your proposal were:

name: name, age: age = {name: "John Smith", age: 42}

rather than:

{name: name, age: age} = {name: "John Smith", age: 42}

=end

#4 Updated by Jack Chen 7 months ago

marcandre (Marc-Andre Lafortune) wrote:

I suggested something similar in .
Here is a summary from my similar suggestion made in :

{key: 'default', otherkey:, **otheroptions} = {otherkey: 42, foo: 'bar'}
key # => 'default'
other
key # => 42
other_options # => {foo: 'bar'}

You'll note that it doesn't give the possibility to map the key to a different variable. Indeed, I don't think that it would be useful and I would rather encourage rubyists to use meanginful option and variable names. It also makes very similar to the way we declare keyword arguments for methods, so no additional learning curve. Your proposal is quite different.

I considered the case of default options, but I couldn't figure out a way to make it read well, and there are many cases where the keys in the hash are not symbols. No value variable after other_key: feels a bit off to me, too.

I'm all for a way to figure out how to get the use case of default options in somehow but I feel that needs more consideration where as this is useful by itself.

#5 Updated by Jack Chen 7 months ago

sawa (Tsuyoshi Sawada) wrote:

=begin
Given that destructive assignments with array prohibits the [ ] on the left side of the assignment, that is:

a, b = [1, 2]

instead of:

[a, b] = [1, 2]

it would be more consistent if your proposal were:

name: name, age: age = {name: "John Smith", age: 42}

rather than:

{name: name, age: age} = {name: "John Smith", age: 42}

=end

I left the braces in because I felt it would be easier to parse, however if without braces is doable as well, that would work also. Will update the proposal.

#6 Updated by Marc-Andre Lafortune 7 months ago

chendo (Jack Chen) wrote:

No value variable after other_key: feels a bit off to me, too.

Not surprising it feels off today, but you better get used to it because it's coming in 2.1.0: https://bugs.ruby-lang.org/issues/7701

#7 Updated by Boris Stitnicky 7 months ago

@whitequark: Hi whitequark, you here? Let me raise my commendations to you for your parser gem!

As for the issue at hand, why not just say:
{ name: "JohnSmith", age: 42 }.!
and have the assigment done:
name = "JohnSmith"
age = 42

If you want the assignment done to diferent variables, why not take Rails's Hash#slice one step further:
{ n: "JohnSmith", a: 42, foo: "bar" }.slice( name: :n, age: :a ) # produces { name: "JohnSmith", age: 42 }
and then
{ n: "JohnSmith", a: 42, foo: "bar" }.slice( name: :n, age: :a ).! # foo: "bar" is ignored away by the #slice method
produces the desired assignment:
name = "JohnSmith"
age = 42

I hope that .! syntax proposal doesn't suck too hard! It might be a general way of making objects perform assignments to local variables. I'm concerned about feature creep, though.

#8 Updated by Charlie Somerville 7 months ago

boris_stitnicky: This sort of feature would be close to impossible to implement in CRuby. I can't speak for JRuby or Rubinius (although I would imagine they're in the same position here) but CRuby relies on being able to statically determine all local variables for a scope ahead of time.

#9 Updated by Boris Stitnicky 7 months ago

@charliesome: I thought myself chendo was stretching it, thanks for making me realize why I felt so. It's all about those famous
a = a #=> nil
cases :-) But... somehow... sorry for a quiche eater like me to say this... I thought that maybe
being able to statically determine local variables is itself a design smell that might need
to be removed from the language... Sorry again for raising issues.

#10 Updated by Alexey Muranov 7 months ago

=begin
How about this:

(x, y, rest, :a => v1, :b => v2, *options) = 1, 2, 3, 4, :a => :foo, :b => :bar, :c => false, :d => true
x # => 1
y # => 2
rest # => [3, 4]
v1 # => :foo
v2 # => :bar
options # => {:c=>false, :d=>true}
=end

#11 Updated by Sean Linsley 4 days ago

This is what I'm imagining:

a, *b, c:, d: 'd', **e = [1, {c: 2}]

a == 1
b == []
c == 2
d == 'd'
e == {} # holds any extras just like `b`

Where an error would be thrown if the hash didn't have the given key, and no default was provided.

Also available in: Atom PDF