From: nobu@... Date: 2020-11-27T02:05:35+00:00 Subject: [ruby-core:101112] [Ruby master Feature#17342] Hash#fetch_set Issue #17342 has been updated by nobu (Nobuyoshi Nakada). Eregon (Benoit Daloze) wrote in #note-8: > Having it as a built-in method, it also makes it possible to avoid computing the key's `#hash` twice, and potentially avoid redoing the lookup in the Hash. It is true **ideally**, but no one can guarantee the hash value never change. --- MaxLap (Maxime Lapointe) wrote in #note-9: > The Hash that we are using is not always under our control. In my second example, I'm using the `request_store` gem, which exposes as `Hash` (`RequestStore.store`), as cache. It would be quite dirty to monkey patch this. I could add that custom Hash in the `store`, but that would be losing the cleaner and shorter code benefits that I'm trying to achieve with this feature request. ```ruby module Caching refine Hash do def cache(key) fetch(key) {self[key] = yield(key)} end end end using Caching RequestStore.store.cache(:monitor_value_is_delayed?) do !MonitorValue.where('date >= ?', Time.now - 5.minutes).exists? end ``` ---------------------------------------- Feature #17342: Hash#fetch_set https://bugs.ruby-lang.org/issues/17342#change-88787 * Author: MaxLap (Maxime Lapointe) * Status: Open * Priority: Normal ---------------------------------------- I would like to propose adding the `fetch_set` method to `Hash`. It behaves just like `fetch`, but when using the default value (2nd argument or the block), it also sets the value in the Hash for the given key. We often use the pattern `cache[key] ||= calculation`. This pattern however has a problem when the calculation could return false or nil, as in those case, the calculation is repeated each time. I believe the best practice in that case is: ```ruby cache.fetch(key) { cache[key] = calculation } ``` With my suggestion, it would be: ```ruby cache.fetch_set(key) { calculation } ``` In these examples, each part is very short, so the `fetch` case is still clean. But as each part gets longer, the need to repeat cache[key] becomes more friction. Here is a more realistic example: ```ruby # Also using the key argument to the block to avoid repeating the # long symbol, adding some indirection RequestStore.store.fetch(:monitor_value_is_delayed?) do |key| RequestStore.store[key] = !MonitorValue.where('date >= ?', Time.now - 5.minutes).exists? end RequestStore.store.fetch_set(:monitor_value_is_delayed?) do !MonitorValue.where('date >= ?', Time.now - 5.minutes).exists? end ``` There is a precedent for such a method: Python has it, but with a quite confusing name: `setdefault(key, default_value)`. This does not set a default for the whole dictionary as the name would make you think, it really just does what is proposed here. https://docs.python.org/3/library/stdtypes.html#dict.setdefault -- https://bugs.ruby-lang.org/ Unsubscribe: