From: "zverok (Victor Shepelev)" Date: 2022-08-17T05:13:47+00:00 Subject: [ruby-core:109509] [Ruby master Feature#17330] Object#non Issue #17330 has been updated by zverok (Victor Shepelev). TBH, I believe that core method names should be short, recognizable and elegant in context in the first place, not explaining what they do in multiple words. We are thinking about "learn once, use 1000 times" here first, not only about a period of introduction of the new method. It is the same as `yield_self`, which kinda "said what it did" but looked ugly and over-explaining for everyday use and was promptly renamed to `then`. The same is related to the idea of `itself_if`/`itself_unless`, I believe (it is like using `each_returning_result` instead of `map`). Just `#if` and `#unless` look tempting, though. ---------------------------------------- Feature #17330: Object#non https://bugs.ruby-lang.org/issues/17330#change-98678 * Author: zverok (Victor Shepelev) * Status: Open * Priority: Normal ---------------------------------------- (As always "with core" method proposals, I don't expect quick success, but hope for a fruitful discussion) ### Reasons: Ruby always tried to be very chainability-friendly. Recently, with introduction of `.then` and `=>`, even more so. But one pattern that frequently emerges and doesn't have good idiomatic expression: calculate something, and if it is not a "good" value, return `nil` (or provide default value with `||`). There are currently two partial solutions: 1. `nonzero?` in Ruby core (frequently mocked for "inadequate" behavior, as it is looking like predicate method, but instead of `true`/`false` returns an original value or `nil`) 2. ActiveSupport `Object#presence`, which also returns an original value or `nil` if it is not "present" (e.g. `nil` or `empty?` in AS-speak) Both of them prove themselves quite useful in some domains, but they are targeting only those particular domains, look unlike each other, and can be confusing. ### Proposal: Method `Object#non` (or `Kernel#non`), which receives a block, calls it with receiver and returns `nil` (if block matched) or receiver otherwise. ##### Prototype implementation: ```ruby class Object def non self unless yield(self) end end ``` ##### Usage examples: 1. With number: ```ruby limit = calculate.some.limit limit.zero? ? DEFAULT_LIMIT : limit # or, with nonzero? calculate.some.limit.nonzero? || DEFAULT_LIMIT # with non: calculate.some.limit.non(&:zero?) || DEFAULT_LIMIT # ^ Note here, how, unlike `nonzero?`, we see predicate-y ?, but it is INSIDE the `non()` and less confusing ``` 2. With string: ```ruby name = params[:name] if params[:name] && !params[:name].empty? # or, with ActiveSupport: name = params[:name].presence # with non: name = params[:name]&.non(&:empty?) ``` 3. More complicated example ```ruby action = payload.dig('action', 'type') return if PROHIBITED_ACTIONS.include?(action) send("do_#{action}") # with non & then: payload.dig('action', 'type') .non { |action| PROHIBITED_ACTIONS.include?(action) } &.then { |action| send("do_#{action}") } ``` Basically, the proposal is a "chainable guard clause" that allows to "chain"ify and DRYify code like: ```ruby value = fetch_something return value unless value.with_problems? # which turns into fetch_something.non(&:with_problems?) # or value = fetch_something value = reasonable_default if value.with_problems? # turns into value = fetch_something.non(&:with_problems?) || reasonable_default ``` I believe that this idiom is frequent enough, in combinations like (assorted examples) "read config file but return `nil` if it is empty/wrong version", "fetch latest invoice, but ignore if it has an `unpayable` flag", "fetch a list of last user's searches, but if it is empty, provide default search hints" etc. I believe there _is_ un unreflected need for idiom like this, the need that is demonstrated by the existence of `nonzero?` and `presence`. -- https://bugs.ruby-lang.org/ Unsubscribe: