ruby-core@ruby-lang.org archive (unofficial mirror)
 help / color / mirror / Atom feed
* [ruby-core:116940] [Ruby master Feature#20300] Hash: set value and get pre-existing value in one call
@ 2024-02-26  2:42 AMomchilov (Alexander Momchilov) via ruby-core
  2024-02-26  4:18 ` [ruby-core:116943] " nobu (Nobuyoshi Nakada) via ruby-core
                   ` (16 more replies)
  0 siblings, 17 replies; 18+ messages in thread
From: AMomchilov (Alexander Momchilov) via ruby-core @ 2024-02-26  2:42 UTC (permalink / raw)
  To: ruby-core; +Cc: AMomchilov (Alexander Momchilov)

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/
 ______________________________________________
 ruby-core mailing list -- ruby-core@ml.ruby-lang.org
 To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
 ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-core.ml.ruby-lang.org/

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [ruby-core:116943] [Ruby master Feature#20300] Hash: set value and get pre-existing value in one call
  2024-02-26  2:42 [ruby-core:116940] [Ruby master Feature#20300] Hash: set value and get pre-existing value in one call AMomchilov (Alexander Momchilov) via ruby-core
@ 2024-02-26  4:18 ` nobu (Nobuyoshi Nakada) via ruby-core
  2024-02-26 10:16 ` [ruby-core:116947] " Eregon (Benoit Daloze) via ruby-core
                   ` (15 subsequent siblings)
  16 siblings, 0 replies; 18+ messages in thread
From: nobu (Nobuyoshi Nakada) via ruby-core @ 2024-02-26  4:18 UTC (permalink / raw)
  To: ruby-core; +Cc: nobu (Nobuyoshi Nakada)

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/
 ______________________________________________
 ruby-core mailing list -- ruby-core@ml.ruby-lang.org
 To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
 ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-core.ml.ruby-lang.org/

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [ruby-core:116947] [Ruby master Feature#20300] Hash: set value and get pre-existing value in one call
  2024-02-26  2:42 [ruby-core:116940] [Ruby master Feature#20300] Hash: set value and get pre-existing value in one call AMomchilov (Alexander Momchilov) via ruby-core
  2024-02-26  4:18 ` [ruby-core:116943] " nobu (Nobuyoshi Nakada) via ruby-core
@ 2024-02-26 10:16 ` Eregon (Benoit Daloze) via ruby-core
  2024-02-26 10:38 ` [ruby-core:116948] " rubyFeedback (robert heiler) via ruby-core
                   ` (14 subsequent siblings)
  16 siblings, 0 replies; 18+ messages in thread
From: Eregon (Benoit Daloze) via ruby-core @ 2024-02-26 10:16 UTC (permalink / raw)
  To: ruby-core; +Cc: Eregon (Benoit Daloze)

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.html#get_and_set-instance_method

`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/
 ______________________________________________
 ruby-core mailing list -- ruby-core@ml.ruby-lang.org
 To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
 ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-core.ml.ruby-lang.org/

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [ruby-core:116948] [Ruby master Feature#20300] Hash: set value and get pre-existing value in one call
  2024-02-26  2:42 [ruby-core:116940] [Ruby master Feature#20300] Hash: set value and get pre-existing value in one call AMomchilov (Alexander Momchilov) via ruby-core
  2024-02-26  4:18 ` [ruby-core:116943] " nobu (Nobuyoshi Nakada) via ruby-core
  2024-02-26 10:16 ` [ruby-core:116947] " Eregon (Benoit Daloze) via ruby-core
@ 2024-02-26 10:38 ` rubyFeedback (robert heiler) via ruby-core
  2024-02-26 16:21 ` [ruby-core:116958] " MaxLap (Maxime Lapointe) via ruby-core
                   ` (13 subsequent siblings)
  16 siblings, 0 replies; 18+ messages in thread
From: rubyFeedback (robert heiler) via ruby-core @ 2024-02-26 10:38 UTC (permalink / raw)
  To: ruby-core; +Cc: rubyFeedback (robert heiler)

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/
 ______________________________________________
 ruby-core mailing list -- ruby-core@ml.ruby-lang.org
 To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
 ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-core.ml.ruby-lang.org/

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [ruby-core:116958] [Ruby master Feature#20300] Hash: set value and get pre-existing value in one call
  2024-02-26  2:42 [ruby-core:116940] [Ruby master Feature#20300] Hash: set value and get pre-existing value in one call AMomchilov (Alexander Momchilov) via ruby-core
                   ` (2 preceding siblings ...)
  2024-02-26 10:38 ` [ruby-core:116948] " rubyFeedback (robert heiler) via ruby-core
@ 2024-02-26 16:21 ` MaxLap (Maxime Lapointe) via ruby-core
  2024-02-27 16:42 ` [ruby-core:116973] " matheusrich (Matheus Richard) via ruby-core
                   ` (12 subsequent siblings)
  16 siblings, 0 replies; 18+ messages in thread
From: MaxLap (Maxime Lapointe) via ruby-core @ 2024-02-26 16:21 UTC (permalink / raw)
  To: ruby-core; +Cc: MaxLap (Maxime Lapointe)

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/
 ______________________________________________
 ruby-core mailing list -- ruby-core@ml.ruby-lang.org
 To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
 ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-core.ml.ruby-lang.org/

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [ruby-core:116973] [Ruby master Feature#20300] Hash: set value and get pre-existing value in one call
  2024-02-26  2:42 [ruby-core:116940] [Ruby master Feature#20300] Hash: set value and get pre-existing value in one call AMomchilov (Alexander Momchilov) via ruby-core
                   ` (3 preceding siblings ...)
  2024-02-26 16:21 ` [ruby-core:116958] " MaxLap (Maxime Lapointe) via ruby-core
@ 2024-02-27 16:42 ` matheusrich (Matheus Richard) via ruby-core
  2024-02-27 16:46 ` [ruby-core:116975] " AMomchilov (Alexander Momchilov) via ruby-core
                   ` (11 subsequent siblings)
  16 siblings, 0 replies; 18+ messages in thread
From: matheusrich (Matheus Richard) via ruby-core @ 2024-02-27 16:42 UTC (permalink / raw)
  To: ruby-core; +Cc: matheusrich (Matheus Richard)

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/
 ______________________________________________
 ruby-core mailing list -- ruby-core@ml.ruby-lang.org
 To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
 ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-core.ml.ruby-lang.org/

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [ruby-core:116975] [Ruby master Feature#20300] Hash: set value and get pre-existing value in one call
  2024-02-26  2:42 [ruby-core:116940] [Ruby master Feature#20300] Hash: set value and get pre-existing value in one call AMomchilov (Alexander Momchilov) via ruby-core
                   ` (4 preceding siblings ...)
  2024-02-27 16:42 ` [ruby-core:116973] " matheusrich (Matheus Richard) via ruby-core
@ 2024-02-27 16:46 ` AMomchilov (Alexander Momchilov) via ruby-core
  2024-02-27 17:34 ` [ruby-core:116978] " matheusrich (Matheus Richard) via ruby-core
                   ` (10 subsequent siblings)
  16 siblings, 0 replies; 18+ messages in thread
From: AMomchilov (Alexander Momchilov) via ruby-core @ 2024-02-27 16:46 UTC (permalink / raw)
  To: ruby-core; +Cc: AMomchilov (Alexander Momchilov)

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(_:forkey:)), 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/
 ______________________________________________
 ruby-core mailing list -- ruby-core@ml.ruby-lang.org
 To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
 ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-core.ml.ruby-lang.org/

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [ruby-core:116978] [Ruby master Feature#20300] Hash: set value and get pre-existing value in one call
  2024-02-26  2:42 [ruby-core:116940] [Ruby master Feature#20300] Hash: set value and get pre-existing value in one call AMomchilov (Alexander Momchilov) via ruby-core
                   ` (5 preceding siblings ...)
  2024-02-27 16:46 ` [ruby-core:116975] " AMomchilov (Alexander Momchilov) via ruby-core
@ 2024-02-27 17:34 ` matheusrich (Matheus Richard) via ruby-core
  2024-02-27 21:30 ` [ruby-core:116981] " AMomchilov (Alexander Momchilov) via ruby-core
                   ` (9 subsequent siblings)
  16 siblings, 0 replies; 18+ messages in thread
From: matheusrich (Matheus Richard) via ruby-core @ 2024-02-27 17:34 UTC (permalink / raw)
  To: ruby-core; +Cc: matheusrich (Matheus Richard)

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/put.html) 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._racket%2Fprivate%2Fmore-scheme..rkt%29._hash-update%29%29) 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/
 ______________________________________________
 ruby-core mailing list -- ruby-core@ml.ruby-lang.org
 To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
 ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-core.ml.ruby-lang.org/

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [ruby-core:116981] [Ruby master Feature#20300] Hash: set value and get pre-existing value in one call
  2024-02-26  2:42 [ruby-core:116940] [Ruby master Feature#20300] Hash: set value and get pre-existing value in one call AMomchilov (Alexander Momchilov) via ruby-core
                   ` (6 preceding siblings ...)
  2024-02-27 17:34 ` [ruby-core:116978] " matheusrich (Matheus Richard) via ruby-core
@ 2024-02-27 21:30 ` AMomchilov (Alexander Momchilov) via ruby-core
  2024-02-27 21:39 ` [ruby-core:116982] " matheusrich (Matheus Richard) via ruby-core
                   ` (8 subsequent siblings)
  16 siblings, 0 replies; 18+ messages in thread
From: AMomchilov (Alexander Momchilov) via ruby-core @ 2024-02-27 21:30 UTC (permalink / raw)
  To: ruby-core; +Cc: AMomchilov (Alexander Momchilov)

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/
 ______________________________________________
 ruby-core mailing list -- ruby-core@ml.ruby-lang.org
 To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
 ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-core.ml.ruby-lang.org/

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [ruby-core:116982] [Ruby master Feature#20300] Hash: set value and get pre-existing value in one call
  2024-02-26  2:42 [ruby-core:116940] [Ruby master Feature#20300] Hash: set value and get pre-existing value in one call AMomchilov (Alexander Momchilov) via ruby-core
                   ` (7 preceding siblings ...)
  2024-02-27 21:30 ` [ruby-core:116981] " AMomchilov (Alexander Momchilov) via ruby-core
@ 2024-02-27 21:39 ` matheusrich (Matheus Richard) via ruby-core
  2024-03-14  5:22 ` [ruby-core:117136] " nobu (Nobuyoshi Nakada) via ruby-core
                   ` (7 subsequent siblings)
  16 siblings, 0 replies; 18+ messages in thread
From: matheusrich (Matheus Richard) via ruby-core @ 2024-02-27 21:39 UTC (permalink / raw)
  To: ruby-core; +Cc: matheusrich (Matheus Richard)

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/
 ______________________________________________
 ruby-core mailing list -- ruby-core@ml.ruby-lang.org
 To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
 ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-core.ml.ruby-lang.org/

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [ruby-core:117136] [Ruby master Feature#20300] Hash: set value and get pre-existing value in one call
  2024-02-26  2:42 [ruby-core:116940] [Ruby master Feature#20300] Hash: set value and get pre-existing value in one call AMomchilov (Alexander Momchilov) via ruby-core
                   ` (8 preceding siblings ...)
  2024-02-27 21:39 ` [ruby-core:116982] " matheusrich (Matheus Richard) via ruby-core
@ 2024-03-14  5:22 ` nobu (Nobuyoshi Nakada) via ruby-core
  2024-03-14 10:12 ` [ruby-core:117156] " mame (Yusuke Endoh) via ruby-core
                   ` (6 subsequent siblings)
  16 siblings, 0 replies; 18+ messages in thread
From: nobu (Nobuyoshi Nakada) via ruby-core @ 2024-03-14  5:22 UTC (permalink / raw)
  To: ruby-core; +Cc: nobu (Nobuyoshi Nakada)

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/
 ______________________________________________
 ruby-core mailing list -- ruby-core@ml.ruby-lang.org
 To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
 ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-core.ml.ruby-lang.org/

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [ruby-core:117156] [Ruby master Feature#20300] Hash: set value and get pre-existing value in one call
  2024-02-26  2:42 [ruby-core:116940] [Ruby master Feature#20300] Hash: set value and get pre-existing value in one call AMomchilov (Alexander Momchilov) via ruby-core
                   ` (9 preceding siblings ...)
  2024-03-14  5:22 ` [ruby-core:117136] " nobu (Nobuyoshi Nakada) via ruby-core
@ 2024-03-14 10:12 ` mame (Yusuke Endoh) via ruby-core
  2024-03-14 15:05 ` [ruby-core:117178] " Eregon (Benoit Daloze) via ruby-core
                   ` (5 subsequent siblings)
  16 siblings, 0 replies; 18+ messages in thread
From: mame (Yusuke Endoh) via ruby-core @ 2024-03-14 10:12 UTC (permalink / raw)
  To: ruby-core; +Cc: mame (Yusuke Endoh)

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/
 ______________________________________________
 ruby-core mailing list -- ruby-core@ml.ruby-lang.org
 To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
 ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-core.ml.ruby-lang.org/

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [ruby-core:117178] [Ruby master Feature#20300] Hash: set value and get pre-existing value in one call
  2024-02-26  2:42 [ruby-core:116940] [Ruby master Feature#20300] Hash: set value and get pre-existing value in one call AMomchilov (Alexander Momchilov) via ruby-core
                   ` (10 preceding siblings ...)
  2024-03-14 10:12 ` [ruby-core:117156] " mame (Yusuke Endoh) via ruby-core
@ 2024-03-14 15:05 ` Eregon (Benoit Daloze) via ruby-core
  2024-03-14 15:06 ` [ruby-core:117179] " Eregon (Benoit Daloze) via ruby-core
                   ` (4 subsequent siblings)
  16 siblings, 0 replies; 18+ messages in thread
From: Eregon (Benoit Daloze) via ruby-core @ 2024-03-14 15:05 UTC (permalink / raw)
  To: ruby-core; +Cc: Eregon (Benoit Daloze)

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/
 ______________________________________________
 ruby-core mailing list -- ruby-core@ml.ruby-lang.org
 To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
 ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-core.ml.ruby-lang.org/

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [ruby-core:117179] [Ruby master Feature#20300] Hash: set value and get pre-existing value in one call
  2024-02-26  2:42 [ruby-core:116940] [Ruby master Feature#20300] Hash: set value and get pre-existing value in one call AMomchilov (Alexander Momchilov) via ruby-core
                   ` (11 preceding siblings ...)
  2024-03-14 15:05 ` [ruby-core:117178] " Eregon (Benoit Daloze) via ruby-core
@ 2024-03-14 15:06 ` Eregon (Benoit Daloze) via ruby-core
  2024-03-15  1:49 ` [ruby-core:117193] " AMomchilov (Alexander Momchilov) via ruby-core
                   ` (3 subsequent siblings)
  16 siblings, 0 replies; 18+ messages in thread
From: Eregon (Benoit Daloze) via ruby-core @ 2024-03-14 15:06 UTC (permalink / raw)
  To: ruby-core; +Cc: Eregon (Benoit Daloze)

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/
 ______________________________________________
 ruby-core mailing list -- ruby-core@ml.ruby-lang.org
 To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
 ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-core.ml.ruby-lang.org/

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [ruby-core:117193] [Ruby master Feature#20300] Hash: set value and get pre-existing value in one call
  2024-02-26  2:42 [ruby-core:116940] [Ruby master Feature#20300] Hash: set value and get pre-existing value in one call AMomchilov (Alexander Momchilov) via ruby-core
                   ` (12 preceding siblings ...)
  2024-03-14 15:06 ` [ruby-core:117179] " Eregon (Benoit Daloze) via ruby-core
@ 2024-03-15  1:49 ` AMomchilov (Alexander Momchilov) via ruby-core
  2024-03-15  2:46 ` [ruby-core:117194] " shyouhei (Shyouhei Urabe) via ruby-core
                   ` (2 subsequent siblings)
  16 siblings, 0 replies; 18+ messages in thread
From: AMomchilov (Alexander Momchilov) via ruby-core @ 2024-03-15  1:49 UTC (permalink / raw)
  To: ruby-core; +Cc: AMomchilov (Alexander Momchilov)

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/
 ______________________________________________
 ruby-core mailing list -- ruby-core@ml.ruby-lang.org
 To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
 ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-core.ml.ruby-lang.org/

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [ruby-core:117194] [Ruby master Feature#20300] Hash: set value and get pre-existing value in one call
  2024-02-26  2:42 [ruby-core:116940] [Ruby master Feature#20300] Hash: set value and get pre-existing value in one call AMomchilov (Alexander Momchilov) via ruby-core
                   ` (13 preceding siblings ...)
  2024-03-15  1:49 ` [ruby-core:117193] " AMomchilov (Alexander Momchilov) via ruby-core
@ 2024-03-15  2:46 ` shyouhei (Shyouhei Urabe) via ruby-core
  2024-04-17  4:30 ` [ruby-core:117545] " nobu (Nobuyoshi Nakada) via ruby-core
  2024-04-17 10:57 ` [ruby-core:117560] " mame (Yusuke Endoh) via ruby-core
  16 siblings, 0 replies; 18+ messages in thread
From: shyouhei (Shyouhei Urabe) via ruby-core @ 2024-03-15  2:46 UTC (permalink / raw)
  To: ruby-core; +Cc: shyouhei (Shyouhei Urabe)

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/
 ______________________________________________
 ruby-core mailing list -- ruby-core@ml.ruby-lang.org
 To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
 ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-core.ml.ruby-lang.org/

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [ruby-core:117545] [Ruby master Feature#20300] Hash: set value and get pre-existing value in one call
  2024-02-26  2:42 [ruby-core:116940] [Ruby master Feature#20300] Hash: set value and get pre-existing value in one call AMomchilov (Alexander Momchilov) via ruby-core
                   ` (14 preceding siblings ...)
  2024-03-15  2:46 ` [ruby-core:117194] " shyouhei (Shyouhei Urabe) via ruby-core
@ 2024-04-17  4:30 ` nobu (Nobuyoshi Nakada) via ruby-core
  2024-04-17 10:57 ` [ruby-core:117560] " mame (Yusuke Endoh) via ruby-core
  16 siblings, 0 replies; 18+ messages in thread
From: nobu (Nobuyoshi Nakada) via ruby-core @ 2024-04-17  4:30 UTC (permalink / raw)
  To: ruby-core; +Cc: nobu (Nobuyoshi Nakada)

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/
 ______________________________________________
 ruby-core mailing list -- ruby-core@ml.ruby-lang.org
 To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
 ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-core.ml.ruby-lang.org/

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [ruby-core:117560] [Ruby master Feature#20300] Hash: set value and get pre-existing value in one call
  2024-02-26  2:42 [ruby-core:116940] [Ruby master Feature#20300] Hash: set value and get pre-existing value in one call AMomchilov (Alexander Momchilov) via ruby-core
                   ` (15 preceding siblings ...)
  2024-04-17  4:30 ` [ruby-core:117545] " nobu (Nobuyoshi Nakada) via ruby-core
@ 2024-04-17 10:57 ` mame (Yusuke Endoh) via ruby-core
  16 siblings, 0 replies; 18+ messages in thread
From: mame (Yusuke Endoh) via ruby-core @ 2024-04-17 10:57 UTC (permalink / raw)
  To: ruby-core; +Cc: mame (Yusuke Endoh)

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/
 ______________________________________________
 ruby-core mailing list -- ruby-core@ml.ruby-lang.org
 To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
 ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-core.ml.ruby-lang.org/

^ permalink raw reply	[flat|nested] 18+ messages in thread

end of thread, other threads:[~2024-04-17 10:57 UTC | newest]

Thread overview: 18+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2024-02-26  2:42 [ruby-core:116940] [Ruby master Feature#20300] Hash: set value and get pre-existing value in one call AMomchilov (Alexander Momchilov) via ruby-core
2024-02-26  4:18 ` [ruby-core:116943] " nobu (Nobuyoshi Nakada) via ruby-core
2024-02-26 10:16 ` [ruby-core:116947] " Eregon (Benoit Daloze) via ruby-core
2024-02-26 10:38 ` [ruby-core:116948] " rubyFeedback (robert heiler) via ruby-core
2024-02-26 16:21 ` [ruby-core:116958] " MaxLap (Maxime Lapointe) via ruby-core
2024-02-27 16:42 ` [ruby-core:116973] " matheusrich (Matheus Richard) via ruby-core
2024-02-27 16:46 ` [ruby-core:116975] " AMomchilov (Alexander Momchilov) via ruby-core
2024-02-27 17:34 ` [ruby-core:116978] " matheusrich (Matheus Richard) via ruby-core
2024-02-27 21:30 ` [ruby-core:116981] " AMomchilov (Alexander Momchilov) via ruby-core
2024-02-27 21:39 ` [ruby-core:116982] " matheusrich (Matheus Richard) via ruby-core
2024-03-14  5:22 ` [ruby-core:117136] " nobu (Nobuyoshi Nakada) via ruby-core
2024-03-14 10:12 ` [ruby-core:117156] " mame (Yusuke Endoh) via ruby-core
2024-03-14 15:05 ` [ruby-core:117178] " Eregon (Benoit Daloze) via ruby-core
2024-03-14 15:06 ` [ruby-core:117179] " Eregon (Benoit Daloze) via ruby-core
2024-03-15  1:49 ` [ruby-core:117193] " AMomchilov (Alexander Momchilov) via ruby-core
2024-03-15  2:46 ` [ruby-core:117194] " shyouhei (Shyouhei Urabe) via ruby-core
2024-04-17  4:30 ` [ruby-core:117545] " nobu (Nobuyoshi Nakada) via ruby-core
2024-04-17 10:57 ` [ruby-core:117560] " mame (Yusuke Endoh) via ruby-core

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).