From: shyouhei@... Date: 2020-12-17T05:28:19+00:00 Subject: [ruby-core:101493] [Ruby master Feature#17342] Hash#fetch_set Issue #17342 has been updated by shyouhei (Shyouhei Urabe). MaxLap (Maxime Lapointe) wrote in #note-19: > matz (Yukihiro Matsumoto) wrote in #note-16: > > Most of the case, `hash[:key] ||= init` works. The exception is that `init` value being false. But it should be rare. > > The problem is not only with `false`, but with `nil` too. > > Anytime you want to cache something and nil (or false) is a possible value (often meaning the data is missing), this problem arises, Matz says it's rare. I'm not sure if he is true, but so far nobody shows any evidence about its rarity. ---------------------------------------- Feature #17342: Hash#fetch_set https://bugs.ruby-lang.org/issues/17342#change-89272 * Author: MaxLap (Maxime Lapointe) * Status: Feedback * 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: