From: "zverok (Victor Shepelev)" Date: 2022-08-16T15:23:15+00:00 Subject: [ruby-core:109501] [Ruby master Feature#17330] Object#non Issue #17330 has been updated by zverok (Victor Shepelev). @mame One thing that is really frequent in Rails codebases is code like this: ```ruby params.presence || DEFAUT_PARAMS # or, quoting from above, what I write as... params[:name]&.non(&:empty?) # in Rails would be... params[:name].presence ``` (not only `params`-related, it is just an example) It relies on Rails-y (and PHP-y, but not Ruby-ish) concept of "every empty object is not _present_". Its existence and popularity is notable. But it is a) very particular (only empty/falsy objects), and at the same time b) very imprecise (empty AND falsy objects, so by the `.presence` you can't tell whether `nil` could be here or not, or `false` could be etc.) I am trying to generalize it to be suitable for the chainable style of computations. The existence of `nonzero?` highlights that Ruby core devs also tried to address the problem. I find that frequently existence of "chainable-friendly" API inspires the design of other APIs, say, Faraday example can be simplified with ```ruby Faraday.get(url).non(&:error?)&.body ``` Which spells as a direct telling what's happening: "get response from URL, [then, it it is] non-error, get the response body" ---------------------------------------- Feature #17330: Object#non https://bugs.ruby-lang.org/issues/17330#change-98669 * 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: