[ruby-core:116940] [Ruby master Feature#20300] Hash: set value and get pre-existing value in one call

Issue #20300 has been reported by AMomchilov (Alexander Momchilov). ---------------------------------------- Feature #20300: Hash: set value and get pre-existing value in one call https://bugs.ruby-lang.org/issues/20300 * Author: AMomchilov (Alexander Momchilov) * Status: Open ---------------------------------------- When using a Hash, sometimes you want to set a new value, *and* see what was already there. Today, you **have** to do this in two steps: ```ruby h = { k: "old value" } # 1. Do a look-up for `:k`. old_value = h[:k] # 2. Do another look-up for `:k`, even though we just did that! h[:k] = "new value" use(old_value) ``` This requires two separate `Hash` look-ups for `:k`. This is fine for symbols, but is expensive if computing `#hash` or `#eql?` is expensive for the key. It's impossible to work around this today from pure Ruby code. I propose adding `Hash#update_value`, which has semantics are similar to this Ruby snippet: ```ruby class Hash def update_value(key, new_value) old_value = self[key] self[key] = new_value old_value end end ``` ... except it'll be implemented in C, with modifications to `tbl_update` that achieves this with a hash-lookup. Here's a PR with a PoC implementation: https://github.com/ruby/ruby/pull/10092 -- https://bugs.ruby-lang.org/

Issue #20300 has been updated by nobu (Nobuyoshi Nakada). The name `update_value` doesn't feel to return the previous value, to me. What about `Hash#exchange_value`? ---------------------------------------- Feature #20300: Hash: set value and get pre-existing value in one call https://bugs.ruby-lang.org/issues/20300#change-106984 * Author: AMomchilov (Alexander Momchilov) * Status: Open ---------------------------------------- When using a Hash, sometimes you want to set a new value, **and** see what was already there. Today, you **have** to do this in two steps: ```ruby h = { k: "old value" } # 1. Do a look-up for `:k`. old_value = h[:k] # 2. Do another look-up for `:k`, even though we just did that! h[:k] = "new value" use(old_value) ``` This requires two separate `Hash` look-ups for `:k`. This is fine for symbols, but is expensive if computing `#hash` or `#eql?` is expensive for the key. It's impossible to work around this today from pure Ruby code. One example use case is `Set#add?`. See https://bugs.ruby-lang.org/issues/20301 for more details. I propose adding `Hash#update_value`, which has semantics are similar to this Ruby snippet: ```ruby class Hash # Exact method name TBD. def update_value(key, new_value) old_value = self[key] self[key] = new_value old_value end end ``` ... except it'll be implemented in C, with modifications to `tbl_update` that achieves this with a hash-lookup. I'm opening to alternative name suggestions. @nobu came up with `exchange_value`, which I think is great. Here's a PR with a PoC implementation: https://github.com/ruby/ruby/pull/10092 ```ruby h = { k: "old value" } # Does only a single hash look-up old_value = h.update_value(:k, "new value") use(old_value) ``` -- https://bugs.ruby-lang.org/

Issue #20300 has been updated by Eregon (Benoit Daloze). FWIW, concurrent-ruby calls this `get_and_set`: https://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Map.htm... `Hash#exchange_value` sounds fine to me too. ---------------------------------------- Feature #20300: Hash: set value and get pre-existing value in one call https://bugs.ruby-lang.org/issues/20300#change-106987 * Author: AMomchilov (Alexander Momchilov) * Status: Open ---------------------------------------- When using a Hash, sometimes you want to set a new value, **and** see what was already there. Today, you **have** to do this in two steps: ```ruby h = { k: "old value" } # 1. Do a look-up for `:k`. old_value = h[:k] # 2. Do another look-up for `:k`, even though we just did that! h[:k] = "new value" use(old_value) ``` This requires two separate `Hash` look-ups for `:k`. This is fine for symbols, but is expensive if computing `#hash` or `#eql?` is expensive for the key. It's impossible to work around this today from pure Ruby code. One example use case is `Set#add?`. See https://bugs.ruby-lang.org/issues/20301 for more details. I propose adding `Hash#update_value`, which has semantics are similar to this Ruby snippet: ```ruby class Hash # Exact method name TBD. def update_value(key, new_value) old_value = self[key] self[key] = new_value old_value end end ``` ... except it'll be implemented in C, with modifications to `tbl_update` that achieves this with a hash-lookup. I'm opening to alternative name suggestions. @nobu came up with `exchange_value`, which I think is great. Here's a PR with a PoC implementation: https://github.com/ruby/ruby/pull/10092 ```ruby h = { k: "old value" } # Does only a single hash look-up old_value = h.update_value(:k, "new value") use(old_value) ``` -- https://bugs.ruby-lang.org/

Issue #20300 has been updated by rubyFeedback (robert heiler). Not so sure about these names. Note that get_and_set() may have people wonder why it is not set_and_get(). :D In regards to .update_value() people could ask why the old value is returned rather than the new one. I guess exchange_value is a bit more specific than the other variants, as people can say "I put in value x, as the new value, and get, in exchange, value y which was the old one". I don't have a better name suggestion though, but just from the alternatives so far I think nobu's suggestion is at the least better than the other ones, in my opinion. I don't have any particular opinion on the suggested functionality; in my own code I kind of keep setters and getters usually separate, or, e. g. first set and then get (or get first, assign to a variable, then set to a new value). It would be nice if we could have some kind of "universal idiom" for the desired functionality though - could make it easier if different programming languages, at the least OOP-centric ones, would use the same name/terminology for that. That would make naming things easier too, or at the least more consistent across programming languages, just like setters and getters are fairly well-established idioms in OOP-centric languages (ruby is kind of multi-paradigm; I don't know how functional programming languages solve this issue of setting and obtaining a new or old value in one go, so I can't comment on that). ---------------------------------------- Feature #20300: Hash: set value and get pre-existing value in one call https://bugs.ruby-lang.org/issues/20300#change-106988 * Author: AMomchilov (Alexander Momchilov) * Status: Open ---------------------------------------- When using a Hash, sometimes you want to set a new value, **and** see what was already there. Today, you **have** to do this in two steps: ```ruby h = { k: "old value" } # 1. Do a look-up for `:k`. old_value = h[:k] # 2. Do another look-up for `:k`, even though we just did that! h[:k] = "new value" use(old_value) ``` This requires two separate `Hash` look-ups for `:k`. This is fine for symbols, but is expensive if computing `#hash` or `#eql?` is expensive for the key. It's impossible to work around this today from pure Ruby code. One example use case is `Set#add?`. See https://bugs.ruby-lang.org/issues/20301 for more details. I propose adding `Hash#update_value`, which has semantics are similar to this Ruby snippet: ```ruby class Hash # Exact method name TBD. def update_value(key, new_value) old_value = self[key] self[key] = new_value old_value end end ``` ... except it'll be implemented in C, with modifications to `tbl_update` that achieves this with a hash-lookup. I'm opening to alternative name suggestions. @nobu came up with `exchange_value`, which I think is great. Here's a PR with a PoC implementation: https://github.com/ruby/ruby/pull/10092 ```ruby h = { k: "old value" } # Does only a single hash look-up old_value = h.update_value(:k, "new value") use(old_value) ``` -- https://bugs.ruby-lang.org/

Issue #20300 has been updated by MaxLap (Maxime Lapointe). In my mind, `get_and_set` is clear as it is in the order the operations are done. But if you want clearer, `get_then_set` puts emphasis on the order. Things like `exchange` and `swap` can sound like swapping the value between two keys. Honestly, I'm not sure I ever needed this. If `fetch_set` was refused, which I frequently need, this likely won't pass either. ---------------------------------------- Feature #20300: Hash: set value and get pre-existing value in one call https://bugs.ruby-lang.org/issues/20300#change-107001 * Author: AMomchilov (Alexander Momchilov) * Status: Open ---------------------------------------- When using a Hash, sometimes you want to set a new value, **and** see what was already there. Today, you **have** to do this in two steps: ```ruby h = { k: "old value" } # 1. Do a look-up for `:k`. old_value = h[:k] # 2. Do another look-up for `:k`, even though we just did that! h[:k] = "new value" use(old_value) ``` This requires two separate `Hash` look-ups for `:k`. This is fine for symbols, but is expensive if computing `#hash` or `#eql?` is expensive for the key. It's impossible to work around this today from pure Ruby code. One example use case is `Set#add?`. See https://bugs.ruby-lang.org/issues/20301 for more details. I propose adding `Hash#update_value`, which has semantics are similar to this Ruby snippet: ```ruby class Hash # Exact method name TBD. def update_value(key, new_value) old_value = self[key] self[key] = new_value old_value end end ``` ... except it'll be implemented in C, with modifications to `tbl_update` that achieves this with a hash-lookup. I'm opening to alternative name suggestions. @nobu came up with `exchange_value`, which I think is great. Here's a PR with a PoC implementation: https://github.com/ruby/ruby/pull/10092 ```ruby h = { k: "old value" } # Does only a single hash look-up old_value = h.update_value(:k, "new value") use(old_value) ``` -- https://bugs.ruby-lang.org/

Issue #20300 has been updated by matheusrich (Matheus Richard). Rust [calls this method `insert`](https://doc.rust-lang.org/std/collections/struct.HashMap.html#method.insert):
Inserts a key-value pair into the map.
If the map did not have this key present, None is returned.
If the map did have this key present, the value is updated, and the old value is returned.
```rs let mut map = HashMap::new(); assert_eq!(map.insert(37, "a"), None); assert_eq!(map.is_empty(), false);
map.insert(37, "b"); assert_eq!(map.insert(37, "c"), Some("b")); assert_eq!(map[&37], "c"); ```
---------------------------------------- Feature #20300: Hash: set value and get pre-existing value in one call https://bugs.ruby-lang.org/issues/20300#change-107020 * Author: AMomchilov (Alexander Momchilov) * Status: Open ---------------------------------------- When using a Hash, sometimes you want to set a new value, **and** see what was already there. Today, you **have** to do this in two steps: ```ruby h = { k: "old value" } # 1. Do a look-up for `:k`. old_value = h[:k] # 2. Do another look-up for `:k`, even though we just did that! h[:k] = "new value" use(old_value) ``` This requires two separate `Hash` look-ups for `:k`. This is fine for symbols, but is expensive if computing `#hash` or `#eql?` is expensive for the key. It's impossible to work around this today from pure Ruby code. One example use case is `Set#add?`. See https://bugs.ruby-lang.org/issues/20301 for more details. I propose adding `Hash#update_value`, which has semantics are similar to this Ruby snippet: ```ruby class Hash # Exact method name TBD. def update_value(key, new_value) old_value = self[key] self[key] = new_value old_value end end ``` ... except it'll be implemented in C, with modifications to `tbl_update` that achieves this with a hash-lookup. I'm opening to alternative name suggestions. @nobu came up with `exchange_value`, which I think is great. Here's a PR with a PoC implementation: https://github.com/ruby/ruby/pull/10092 ```ruby h = { k: "old value" } # Does only a single hash look-up old_value = h.update_value(:k, "new value") use(old_value) ``` -- https://bugs.ruby-lang.org/

Issue #20300 has been updated by AMomchilov (Alexander Momchilov). @matheusrich I like that name in general, but it's really similar to the existing [`Hash#store`](https://ruby-doc.org/3.3.0/Hash.html#method-i-store) and the distinction is non-obvious. Swift calls this [`updateValue(_:forKey:)`](https://developer.apple.com/documentation/swift/dictionary/updatevalue(_:for...:)), which returns a `Value?` ("the value that was replaced, or `nil` if a new key-value pair was added."), but it has a similar problem. I think `#exchange_value` is the best recommendation yet, because it makes it reasonably easy to guess what the return value would be, even without reading the docs. ---------------------------------------- Feature #20300: Hash: set value and get pre-existing value in one call https://bugs.ruby-lang.org/issues/20300#change-107022 * Author: AMomchilov (Alexander Momchilov) * Status: Open ---------------------------------------- When using a Hash, sometimes you want to set a new value, **and** see what was already there. Today, you **have** to do this in two steps: ```ruby h = { k: "old value" } # 1. Do a look-up for `:k`. old_value = h[:k] # 2. Do another look-up for `:k`, even though we just did that! h[:k] = "new value" use(old_value) ``` This requires two separate `Hash` look-ups for `:k`. This is fine for symbols, but is expensive if computing `#hash` or `#eql?` is expensive for the key. It's impossible to work around this today from pure Ruby code. One example use case is `Set#add?`. See https://bugs.ruby-lang.org/issues/20301 for more details. I propose adding `Hash#update_value`, which has semantics are similar to this Ruby snippet: ```ruby class Hash # Exact method name TBD. def update_value(key, new_value) old_value = self[key] self[key] = new_value old_value end end ``` ... except it'll be implemented in C, with modifications to `tbl_update` that achieves this with a hash-lookup. I'm opening to alternative name suggestions. @nobu came up with `exchange_value`, which I think is great. Here's a PR with a PoC implementation: https://github.com/ruby/ruby/pull/10092 ```ruby h = { k: "old value" } # Does only a single hash look-up old_value = h.update_value(:k, "new value") use(old_value) ``` -- https://bugs.ruby-lang.org/

Issue #20300 has been updated by matheusrich (Matheus Richard). Some other examples from other languages: - [Java](https://docs.oracle.com/javase/8/docs/api/java/util/HashMap.html#put-K-V-) and [Kotlin](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-hash-map/pu...) call this `put`. - [Groovy](https://docs.groovy-lang.org/latest/html/groovy-jdk/java/util/Map.html) calls it `putAt` - [Elixir](https://hexdocs.pm/elixir/1.12/Map.html#get_and_update/3) has an interesting `get_and_update`, which is very descriptive, but also accepts a function argument, which can be used to operate on the old value before returning it. I could see us supporting both cases: ```rb hash.get_and_update(:key, :new_value) hash.get_and_update(:key) { |old| old.upcase } ``` - [Clojure](https://clojuredocs.org/clojure.core/update-in) does something similar with `update-in`. - [Racket](https://docs.racket-lang.org/reference/hashtables.html#%28def._%28%28lib._ra...) calls it `hash-update`. - [Haskell](https://hackage.haskell.org/package/containers-0.4.0.0/docs/Data-Map.html) calls this `insertLookupWithKey` š --- I like `exchange_value` too. While a bit more verbose, having the `_value` suffix opens the opportunity to add a similar method for keys in the future Another suggestion is `replace_value`, since we already have `replace` on hash. ---------------------------------------- Feature #20300: Hash: set value and get pre-existing value in one call https://bugs.ruby-lang.org/issues/20300#change-107026 * Author: AMomchilov (Alexander Momchilov) * Status: Open ---------------------------------------- When using a Hash, sometimes you want to set a new value, **and** see what was already there. Today, you **have** to do this in two steps: ```ruby h = { k: "old value" } # 1. Do a look-up for `:k`. old_value = h[:k] # 2. Do another look-up for `:k`, even though we just did that! h[:k] = "new value" use(old_value) ``` This requires two separate `Hash` look-ups for `:k`. This is fine for symbols, but is expensive if computing `#hash` or `#eql?` is expensive for the key. It's impossible to work around this today from pure Ruby code. One example use case is `Set#add?`. See https://bugs.ruby-lang.org/issues/20301 for more details. I propose adding `Hash#update_value`, which has semantics are similar to this Ruby snippet: ```ruby class Hash # Exact method name TBD. def update_value(key, new_value) old_value = self[key] self[key] = new_value old_value end end ``` ... except it'll be implemented in C, with modifications to `tbl_update` that achieves this with a hash-lookup. I'm opening to alternative name suggestions. @nobu came up with `exchange_value`, which I think is great. Here's a PR with a PoC implementation: https://github.com/ruby/ruby/pull/10092 ```ruby h = { k: "old value" } # Does only a single hash look-up old_value = h.update_value(:k, "new value") use(old_value) ``` -- https://bugs.ruby-lang.org/

Issue #20300 has been updated by AMomchilov (Alexander Momchilov). @matheusrich Thanks for putting together that list! In general, I like the idea of having this method support an optional block, but we would need to benchmark it to ensure it doesn't have an undesired slow down in the `exchange_value(:key, :new_value)` case. Really, this API's main goal is performance (since it doesn't do anything you couldn't already do with a combination of `#has_key?`, `#[]` and `#[]=`), so if an optional block arg slows that down, it would entirely defeat its benefit. ---------------------------------------- Feature #20300: Hash: set value and get pre-existing value in one call https://bugs.ruby-lang.org/issues/20300#change-107029 * Author: AMomchilov (Alexander Momchilov) * Status: Open ---------------------------------------- When using a Hash, sometimes you want to set a new value, **and** see what was already there. Today, you **have** to do this in two steps: ```ruby h = { k: "old value" } # 1. Do a look-up for `:k`. old_value = h[:k] # 2. Do another look-up for `:k`, even though we just did that! h[:k] = "new value" use(old_value) ``` This requires two separate `Hash` look-ups for `:k`. This is fine for symbols, but is expensive if computing `#hash` or `#eql?` is expensive for the key. It's impossible to work around this today from pure Ruby code. One example use case is `Set#add?`. See https://bugs.ruby-lang.org/issues/20301 for more details. I propose adding `Hash#update_value`, which has semantics are similar to this Ruby snippet: ```ruby class Hash # Exact method name TBD. def update_value(key, new_value) old_value = self[key] self[key] = new_value old_value end end ``` ... except it'll be implemented in C, with modifications to `tbl_update` that achieves this with a hash-lookup. I'm opening to alternative name suggestions. @nobu came up with `exchange_value`, which I think is great. Here's a PR with a PoC implementation: https://github.com/ruby/ruby/pull/10092 ```ruby h = { k: "old value" } # Does only a single hash look-up old_value = h.update_value(:k, "new value") use(old_value) ``` -- https://bugs.ruby-lang.org/

Issue #20300 has been updated by matheusrich (Matheus Richard). `#get_and_transform` could be a name for the version with the block, if performance is impacted significantly. ---------------------------------------- Feature #20300: Hash: set value and get pre-existing value in one call https://bugs.ruby-lang.org/issues/20300#change-107030 * Author: AMomchilov (Alexander Momchilov) * Status: Open ---------------------------------------- When using a Hash, sometimes you want to set a new value, **and** see what was already there. Today, you **have** to do this in two steps: ```ruby h = { k: "old value" } # 1. Do a look-up for `:k`. old_value = h[:k] # 2. Do another look-up for `:k`, even though we just did that! h[:k] = "new value" use(old_value) ``` This requires two separate `Hash` look-ups for `:k`. This is fine for symbols, but is expensive if computing `#hash` or `#eql?` is expensive for the key. It's impossible to work around this today from pure Ruby code. One example use case is `Set#add?`. See https://bugs.ruby-lang.org/issues/20301 for more details. I propose adding `Hash#update_value`, which has semantics are similar to this Ruby snippet: ```ruby class Hash # Exact method name TBD. def update_value(key, new_value) old_value = self[key] self[key] = new_value old_value end end ``` ... except it'll be implemented in C, with modifications to `tbl_update` that achieves this with a hash-lookup. I'm opening to alternative name suggestions. @nobu came up with `exchange_value`, which I think is great. Here's a PR with a PoC implementation: https://github.com/ruby/ruby/pull/10092 ```ruby h = { k: "old value" } # Does only a single hash look-up old_value = h.update_value(:k, "new value") use(old_value) ``` -- https://bugs.ruby-lang.org/

Issue #20300 has been updated by nobu (Nobuyoshi Nakada). I want `ENV.exchange_value` rather than `Hash#exchange_value`. ---------------------------------------- Feature #20300: Hash: set value and get pre-existing value in one call https://bugs.ruby-lang.org/issues/20300#change-107215 * Author: AMomchilov (Alexander Momchilov) * Status: Open ---------------------------------------- When using a Hash, sometimes you want to set a new value, **and** see what was already there. Today, you **have** to do this in two steps: ```ruby h = { k: "old value" } # 1. Do a look-up for `:k`. old_value = h[:k] # 2. Do another look-up for `:k`, even though we just did that! h[:k] = "new value" use(old_value) ``` This requires two separate `Hash` look-ups for `:k`. This is fine for symbols, but is expensive if computing `#hash` or `#eql?` is expensive for the key. It's impossible to work around this today from pure Ruby code. One example use case is `Set#add?`. See https://bugs.ruby-lang.org/issues/20301 for more details. I propose adding `Hash#exchange_value`, which has semantics are similar to this Ruby snippet: ```ruby class Hash # Exact method name TBD. def exchange_value(key, new_value) old_value = self[key] self[key] = new_value old_value end end ``` ... except it'll be implemented in C, with modifications to `tbl_update` that achieves this with a hash-lookup. I'm opening to alternative name suggestions. @nobu came up with `exchange_value`, which I think is great. Here's a PR with a PoC implementation: https://github.com/ruby/ruby/pull/10092 ```ruby h = { k: "old value" } # Does only a single hash look-up old_value = h.exchange_value(:k, "new value") use(old_value) ``` -- https://bugs.ruby-lang.org/

Issue #20300 has been updated by mame (Yusuke Endoh). Discussed at developers meeting. @shyouhei pointed out that `Set#add?` could be improved with a single lookup by looking at the change in size, without this proposal. https://bugs.ruby-lang.org/issues/20301#note-8 @matz said that we need another good use case to introduce this if it is sufficient for `Set#add?`. ---------------------------------------- Feature #20300: Hash: set value and get pre-existing value in one call https://bugs.ruby-lang.org/issues/20300#change-107243 * Author: AMomchilov (Alexander Momchilov) * Status: Open ---------------------------------------- When using a Hash, sometimes you want to set a new value, **and** see what was already there. Today, you **have** to do this in two steps: ```ruby h = { k: "old value" } # 1. Do a look-up for `:k`. old_value = h[:k] # 2. Do another look-up for `:k`, even though we just did that! h[:k] = "new value" use(old_value) ``` This requires two separate `Hash` look-ups for `:k`. This is fine for symbols, but is expensive if computing `#hash` or `#eql?` is expensive for the key. It's impossible to work around this today from pure Ruby code. One example use case is `Set#add?`. See https://bugs.ruby-lang.org/issues/20301 for more details. I propose adding `Hash#exchange_value`, which has semantics are similar to this Ruby snippet: ```ruby class Hash # Exact method name TBD. def exchange_value(key, new_value) old_value = self[key] self[key] = new_value old_value end end ``` ... except it'll be implemented in C, with modifications to `tbl_update` that achieves this with a hash-lookup. I'm opening to alternative name suggestions. @nobu came up with `exchange_value`, which I think is great. Here's a PR with a PoC implementation: https://github.com/ruby/ruby/pull/10092 ```ruby h = { k: "old value" } # Does only a single hash look-up old_value = h.exchange_value(:k, "new value") use(old_value) ``` -- https://bugs.ruby-lang.org/

Issue #20300 has been updated by Eregon (Benoit Daloze). That implementation is incorrect (i.e. less correct than current implemenation), and `Hash#exchange_value` is needed to be thread-safe for Set, see https://bugs.ruby-lang.org/issues/20301. ---------------------------------------- Feature #20300: Hash: set value and get pre-existing value in one call https://bugs.ruby-lang.org/issues/20300#change-107268 * Author: AMomchilov (Alexander Momchilov) * Status: Open ---------------------------------------- When using a Hash, sometimes you want to set a new value, **and** see what was already there. Today, you **have** to do this in two steps: ```ruby h = { k: "old value" } # 1. Do a look-up for `:k`. old_value = h[:k] # 2. Do another look-up for `:k`, even though we just did that! h[:k] = "new value" use(old_value) ``` This requires two separate `Hash` look-ups for `:k`. This is fine for symbols, but is expensive if computing `#hash` or `#eql?` is expensive for the key. It's impossible to work around this today from pure Ruby code. One example use case is `Set#add?`. See https://bugs.ruby-lang.org/issues/20301 for more details. I propose adding `Hash#exchange_value`, which has semantics are similar to this Ruby snippet: ```ruby class Hash # Exact method name TBD. def exchange_value(key, new_value) old_value = self[key] self[key] = new_value old_value end end ``` ... except it'll be implemented in C, with modifications to `tbl_update` that achieves this with a hash-lookup. I'm opening to alternative name suggestions. @nobu came up with `exchange_value`, which I think is great. Here's a PR with a PoC implementation: https://github.com/ruby/ruby/pull/10092 ```ruby h = { k: "old value" } # Does only a single hash look-up old_value = h.exchange_value(:k, "new value") use(old_value) ``` -- https://bugs.ruby-lang.org/

Issue #20300 has been updated by Eregon (Benoit Daloze). nobu (Nobuyoshi Nakada) wrote in #note-17:
I want `ENV.exchange_value` rather than `Hash#exchange_value`.
Agreed it's a generally useful thing. So I think we should add it for both Hash and ENV (ENV has all `Hash` methods anyway). ---------------------------------------- Feature #20300: Hash: set value and get pre-existing value in one call https://bugs.ruby-lang.org/issues/20300#change-107269 * Author: AMomchilov (Alexander Momchilov) * Status: Open ---------------------------------------- When using a Hash, sometimes you want to set a new value, **and** see what was already there. Today, you **have** to do this in two steps: ```ruby h = { k: "old value" } # 1. Do a look-up for `:k`. old_value = h[:k] # 2. Do another look-up for `:k`, even though we just did that! h[:k] = "new value" use(old_value) ``` This requires two separate `Hash` look-ups for `:k`. This is fine for symbols, but is expensive if computing `#hash` or `#eql?` is expensive for the key. It's impossible to work around this today from pure Ruby code. One example use case is `Set#add?`. See https://bugs.ruby-lang.org/issues/20301 for more details. I propose adding `Hash#exchange_value`, which has semantics are similar to this Ruby snippet: ```ruby class Hash # Exact method name TBD. def exchange_value(key, new_value) old_value = self[key] self[key] = new_value old_value end end ``` ... except it'll be implemented in C, with modifications to `tbl_update` that achieves this with a hash-lookup. I'm opening to alternative name suggestions. @nobu came up with `exchange_value`, which I think is great. Here's a PR with a PoC implementation: https://github.com/ruby/ruby/pull/10092 ```ruby h = { k: "old value" } # Does only a single hash look-up old_value = h.exchange_value(:k, "new value") use(old_value) ``` -- https://bugs.ruby-lang.org/

Issue #20300 has been updated by AMomchilov (Alexander Momchilov). nobu (Nobuyoshi Nakada) wrote in #note-17:
I want `ENV.exchange_value` rather than `Hash#exchange_value`.
Is there a reason to put it on `ENV`, but *not* on `Hash`? That seems like a needless restriction to me. ---------------------------------------- Feature #20300: Hash: set value and get pre-existing value in one call https://bugs.ruby-lang.org/issues/20300#change-107282 * Author: AMomchilov (Alexander Momchilov) * Status: Open ---------------------------------------- When using a Hash, sometimes you want to set a new value, **and** see what was already there. Today, you **have** to do this in two steps: ```ruby h = { k: "old value" } # 1. Do a look-up for `:k`. old_value = h[:k] # 2. Do another look-up for `:k`, even though we just did that! h[:k] = "new value" use(old_value) ``` This requires two separate `Hash` look-ups for `:k`. This is fine for symbols, but is expensive if computing `#hash` or `#eql?` is expensive for the key. It's impossible to work around this today from pure Ruby code. One example use case is `Set#add?`. See https://bugs.ruby-lang.org/issues/20301 for more details. I propose adding `Hash#exchange_value`, which has semantics are similar to this Ruby snippet: ```ruby class Hash # Exact method name TBD. def exchange_value(key, new_value) old_value = self[key] self[key] = new_value old_value end end ``` ... except it'll be implemented in C, with modifications to `tbl_update` that achieves this with a hash-lookup. I'm opening to alternative name suggestions. @nobu came up with `exchange_value`, which I think is great. Here's a PR with a PoC implementation: https://github.com/ruby/ruby/pull/10092 ```ruby h = { k: "old value" } # Does only a single hash look-up old_value = h.exchange_value(:k, "new value") use(old_value) ``` -- https://bugs.ruby-lang.org/

Issue #20300 has been updated by shyouhei (Shyouhei Urabe). I'm neutral to this particular method (could be handy). If thread safety is in mind perhaps atomic compare-and-exchange could be better than just exchange, though. ---------------------------------------- Feature #20300: Hash: set value and get pre-existing value in one call https://bugs.ruby-lang.org/issues/20300#change-107283 * Author: AMomchilov (Alexander Momchilov) * Status: Open ---------------------------------------- When using a Hash, sometimes you want to set a new value, **and** see what was already there. Today, you **have** to do this in two steps: ```ruby h = { k: "old value" } # 1. Do a look-up for `:k`. old_value = h[:k] # 2. Do another look-up for `:k`, even though we just did that! h[:k] = "new value" use(old_value) ``` This requires two separate `Hash` look-ups for `:k`. This is fine for symbols, but is expensive if computing `#hash` or `#eql?` is expensive for the key. It's impossible to work around this today from pure Ruby code. One example use case is `Set#add?`. See https://bugs.ruby-lang.org/issues/20301 for more details. I propose adding `Hash#exchange_value`, which has semantics are similar to this Ruby snippet: ```ruby class Hash # Exact method name TBD. def exchange_value(key, new_value) old_value = self[key] self[key] = new_value old_value end end ``` ... except it'll be implemented in C, with modifications to `tbl_update` that achieves this with a hash-lookup. I'm opening to alternative name suggestions. @nobu came up with `exchange_value`, which I think is great. Here's a PR with a PoC implementation: https://github.com/ruby/ruby/pull/10092 ```ruby h = { k: "old value" } # Does only a single hash look-up old_value = h.exchange_value(:k, "new value") use(old_value) ``` -- https://bugs.ruby-lang.org/

Issue #20300 has been updated by nobu (Nobuyoshi Nakada). AMomchilov (Alexander Momchilov) wrote in #note-21:
nobu (Nobuyoshi Nakada) wrote in #note-17:
I want `ENV.exchange_value` rather than `Hash#exchange_value`.
Is there a reason to put it on `ENV`, but *not* on `Hash`? That seems like a needless restriction to me.
`ENV` is a hash-like object, so I'm for `Hash#exchange` too. ---------------------------------------- Feature #20300: Hash: set value and get pre-existing value in one call https://bugs.ruby-lang.org/issues/20300#change-107938 * Author: AMomchilov (Alexander Momchilov) * Status: Open ---------------------------------------- When using a Hash, sometimes you want to set a new value, **and** see what was already there. Today, you **have** to do this in two steps: ```ruby h = { k: "old value" } # 1. Do a look-up for `:k`. old_value = h[:k] # 2. Do another look-up for `:k`, even though we just did that! h[:k] = "new value" use(old_value) ``` This requires two separate `Hash` look-ups for `:k`. This is fine for symbols, but is expensive if computing `#hash` or `#eql?` is expensive for the key. It's impossible to work around this today from pure Ruby code. One example use case is `Set#add?`. See https://bugs.ruby-lang.org/issues/20301 for more details. I propose adding `Hash#exchange_value`, which has semantics are similar to this Ruby snippet: ```ruby class Hash # Exact method name TBD. def exchange_value(key, new_value) old_value = self[key] self[key] = new_value old_value end end ``` ... except it'll be implemented in C, with modifications to `tbl_update` that achieves this with a hash-lookup. I'm opening to alternative name suggestions. @nobu came up with `exchange_value`, which I think is great. Here's a PR with a PoC implementation: https://github.com/ruby/ruby/pull/10092 ```ruby h = { k: "old value" } # Does only a single hash look-up old_value = h.exchange_value(:k, "new value") use(old_value) ``` -- https://bugs.ruby-lang.org/

Issue #20300 has been updated by mame (Yusuke Endoh). @matz said he was not sure what a name is good for this method because its true motivation is unclear. It was originally intended as a method to improve the efficiency of `Set#add?`, but the use case was shifted to a method for thread safety. This history makes the use case less persuasive. If the main purpose is thread safety, we want to respect the terminology of the parallel computing area. If it is just an internal method for efficiency, a long and verbose name may be preferred. (If it is a daily-use method, a short and convenient name may be preferred.) You may want to explain the concrete example of the use case of thread safety for Hash value exchange, this proposed API is really sufficient for that example use case, and what API and name are given to similar feature in other languages. ---------------------------------------- Feature #20300: Hash: set value and get pre-existing value in one call https://bugs.ruby-lang.org/issues/20300#change-107959 * Author: AMomchilov (Alexander Momchilov) * Status: Open ---------------------------------------- When using a Hash, sometimes you want to set a new value, **and** see what was already there. Today, you **have** to do this in two steps: ```ruby h = { k: "old value" } # 1. Do a look-up for `:k`. old_value = h[:k] # 2. Do another look-up for `:k`, even though we just did that! h[:k] = "new value" use(old_value) ``` This requires two separate `Hash` look-ups for `:k`. This is fine for symbols, but is expensive if computing `#hash` or `#eql?` is expensive for the key. It's impossible to work around this today from pure Ruby code. One example use case is `Set#add?`. See https://bugs.ruby-lang.org/issues/20301 for more details. I propose adding `Hash#exchange_value`, which has semantics are similar to this Ruby snippet: ```ruby class Hash # Exact method name TBD. def exchange_value(key, new_value) old_value = self[key] self[key] = new_value old_value end end ``` ... except it'll be implemented in C, with modifications to `tbl_update` that achieves this with a hash-lookup. I'm opening to alternative name suggestions. @nobu came up with `exchange_value`, which I think is great. Here's a PR with a PoC implementation: https://github.com/ruby/ruby/pull/10092 ```ruby h = { k: "old value" } # Does only a single hash look-up old_value = h.exchange_value(:k, "new value") use(old_value) ``` -- https://bugs.ruby-lang.org/

Issue #20300 has been updated by AMomchilov (Alexander Momchilov). mame (Yusuke Endoh) wrote in #note-24:
It was originally intended as a method to improve the efficiency of `Set#add?`, but the use case was shifted to a method for thread safety. This history makes the use case less persuasive.
The immediate need and use case is for that performance improvement to Set. The discussion of thread safety is just an incidental benefit of this approach, as compared to the count-based alternative that was proposed.
If it is just an internal method for efficiency, a long and verbose name may be preferred.
I would be okay with this, if thereās no appetite for a public, short-named API for this. Perhaps some underscored name that communicates that this is ālibrary internalā to the stdlib.
You may want to explain the concrete example of the use case of thread safety for Hash value exchange, this proposed API is really sufficient for that example use case,
I donāt think I can make a better case than the concrete implementation Iāve already proposed, and its immediate application to the Set performance problem Iāve identified and fixed, using this API. If others want to chime in with the use cases in they have in mind, Iād love to hear those.
and what API and name are given to similar feature in other languages.
Letās first decide if we want this API, in *any* form. Once we agree on that, Iād be happy to dive into details like naming, and comparisons to other languages. ---------------------------------------- Feature #20300: Hash: set value and get pre-existing value in one call https://bugs.ruby-lang.org/issues/20300#change-108239 * Author: AMomchilov (Alexander Momchilov) * Status: Open ---------------------------------------- When using a Hash, sometimes you want to set a new value, **and** see what was already there. Today, you **have** to do this in two steps: ```ruby h = { k: "old value" } # 1. Do a look-up for `:k`. old_value = h[:k] # 2. Do another look-up for `:k`, even though we just did that! h[:k] = "new value" use(old_value) ``` This requires two separate `Hash` look-ups for `:k`. This is fine for symbols, but is expensive if computing `#hash` or `#eql?` is expensive for the key. It's impossible to work around this today from pure Ruby code. One example use case is `Set#add?`. See https://bugs.ruby-lang.org/issues/20301 for more details. I propose adding `Hash#exchange_value`, which has semantics are similar to this Ruby snippet: ```ruby class Hash # Exact method name TBD. def exchange_value(key, new_value) old_value = self[key] self[key] = new_value old_value end end ``` ... except it'll be implemented in C, with modifications to `tbl_update` that achieves this with a hash-lookup. I'm opening to alternative name suggestions. @nobu came up with `exchange_value`, which I think is great. Here's a PR with a PoC implementation: https://github.com/ruby/ruby/pull/10092 ```ruby h = { k: "old value" } # Does only a single hash look-up old_value = h.exchange_value(:k, "new value") use(old_value) ``` -- https://bugs.ruby-lang.org/
participants (8)
-
AMomchilov (Alexander Momchilov)
-
Eregon (Benoit Daloze)
-
mame (Yusuke Endoh)
-
matheusrich (Matheus Richard)
-
MaxLap (Maxime Lapointe)
-
nobu (Nobuyoshi Nakada)
-
rubyFeedback (robert heiler)
-
shyouhei (Shyouhei Urabe)