Project

General

Profile

Actions

Feature #20738

closed

Removing a specific entry from a hash literal

Added by ursm (Keita Urashima) 3 months ago. Updated about 1 month ago.

Status:
Rejected
Assignee:
-
Target version:
-
[ruby-core:119168]

Description

Sometimes I want to decide whether or not to add a particular entry to a hash depending on a condition. If the entire hash does not use nil, I can use Hash#compact.

{
  foo: 1,
  bar: bar? ? 2 : nil
}.compact

But if I want to remove only a specific entry while leaving the other nil, it is somewhat cumbersome. I have to either assign the hash once and change it destructively, or use Hash#reject.

h = {
  foo: 1,
  baz: nil
}

h[:bar] = 2 if bar?
{
  foo: 1,
  bar: bar? ? 2 : :drop,
  baz: nil
}.reject {|_, v| v == :drop }

As a suggestion, how about a special value that indicates an invalid key for the hash? With this, the above example could be written like this:

{
  foo: 1,
  bar: bar? ? 2 : Hash::DROP,
  baz: nil
} #=> {foo: 1, baz: nil}

Updated by nobu (Nobuyoshi Nakada) 3 months ago

"A special value" doesn't feel like a good idea to me.

Updated by osyo (manga osyo) 3 months ago

How about using **?

def bar? = false

{
  foo: 1,
  **(bar? ? { bar: 2 } : {})
}
# => {:foo=>1}

Also, you can use **nil in ruby 3.4-dev.

def bar? = false

{
  foo: 1,
  **({ bar: 2 } if bar?)
}
# => {:foo=>1}

see:

Updated by ursm (Keita Urashima) 3 months ago

Yes, I sometimes do that as well. However, I am not happy that the shape of the resulting hash is unclear.

Updated by ursm (Keita Urashima) 3 months ago

nobu (Nobuyoshi Nakada) wrote in #note-1:

"A special value" doesn't feel like a good idea to me.

Hmmm, does that mean we should extend the syntax? For example, something like this?

{
  foo: 1,
  ?bar: nil
} #=> {foo: 1}

Updated by ursm (Keita Urashima) 3 months ago

With the previous idea, I can't have both removing entries and returning nil depending on the condition.

# If user.child? is true and user.parent is nil, I want parent_name: nil, but the entry is removed.
{
  ?parent_name: user.child? ? user.parent&.name : nil
}

Updated by mame (Yusuke Endoh) 3 months ago

History. Long ago in Ruby, such a special value actually existed. It was nil.

# ruby 1.4.6
h = { 1 => 2 }

p h  #=> {1=>2}

# This removes the key 1
h[1] = nil

p h  #=> {}

However, there were more and more cases where we want to treat nil as an ordinary value. Finally, nil has lost this speciality.

Considering this history, the special value is not a good idea.

Updated by ursm (Keita Urashima) 3 months ago

I believe that the following two points will prevent the same problems as in the past:

  1. Use a value that is never used (e.g., Hash::DROP) instead of nil.
  2. Special treatment of “special value” only if the hash is constructed with literals.
{
  foo: Hash::DROP
} #=> {}

h = {}
h[:foo] = Hash::DROP
h #=> {foo: Hash::DROP}

Note that I am not concerned with the “special value” approach. If there is a better way, please let me know.

Updated by Eregon (Benoit Daloze) 3 months ago · Edited

I don't think it's OK to magically drop entries from a literal based on some value, it's way too surprising.
Notably this would make the capacity of the literal potentially wrong, etc.
And of course Hash::DROP could leak into some variable and then unintentionally drop an entry, that'd be horrible to debug.

{
  foo: 1,
  **({ bar: 2 } if bar?)
}
# OR
h[:bar] = 2 if bar?

Sounds good enough to me.

I don't think it's very frequent to need this to warrant a syntax change either.

Updated by jeremyevans0 (Jeremy Evans) 3 months ago

Updated by byroot (Jean Boussier) 3 months ago

There are several Rails codes that can be improved with this feature.

As one of the maintainer of the code you linked to, I agree with @jeremyevans0 (Jeremy Evans) that it's currently fine as it is. I wouldn't use such feature if it was added.

Updated by ursm (Keita Urashima) 3 months ago

I would like to offer that as I used a simple grep pattern, I could only find simple examples. I wanted to show that this is not as rare as it seems.

Updated by ursm (Keita Urashima) 3 months ago

It would be better to explain the motive. This is an appropriate code.

{
  foo: 1
  bar: 2
}

This is not a mistake, but it's a circuitous code.

h = {}
h[:foo] = 1
h[:bar] = 2
h

The gist of this proposal is that if a condition is included, it should be written in the same form as the “appropriate code”.

h = {}
h[:foo] = 1
h[:bar] = 2 if bar?
h

Updated by matz (Yukihiro Matsumoto) 3 months ago

I don't want to add a special value (Hash::DROP) nor special syntax (?key:) here. Use h = {foo: 1}; h[:bar] = 2 if bar?.

Matz.

Updated by ursm (Keita Urashima) 2 months ago

OK, I'm sorry to hear that, but I'm glad to hear your opinions. Thanks.

Actions #16

Updated by mame (Yusuke Endoh) about 1 month ago

  • Status changed from Open to Rejected
Actions

Also available in: Atom PDF

Like0
Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0