From: daniel@...42.com Date: 2021-01-25T15:54:54+00:00 Subject: [ruby-core:102236] [Ruby master Feature#17330] Object#non Issue #17330 has been updated by Dan0042 (Daniel DeLorme). FWIW, +1 from me At first I thought the only uses were `non(&:zero?)` and `non(&:empty?)` which, while I find very elegant, are not enough to justify adding a method to Kernel. But I think zverok has shown enough other uses to make a convincing case. There might be something to say about how this provides an automatic complement to any predicate method: ```ruby if user.subscription.non(&:expired?) # is more grammatically correct (English-wise) if !user.subscription.expired? # than the traditional negation if user.subscription.expired?.! # or akr style if user.subscription&.non(&:expired?) # particularly useful in chain if s = user.subscription and !s.expired? # the alternatives if user.subscription&.then{ |s| s if !s.expired? } # are not very if user.subscription&.then{ _1 if !_1.expired? } # attractive (IMO) ``` Overall this "singular reject/select" goes in the same direction as `tap` and `then`, so if those two are considered improvements to ruby, I think `non` and `when` would be the same kind of improvement. Can we also consider `non(:empty?)` in the same vein as `inject`, or is that an anti-pattern? ---------------------------------------- Feature #17330: Object#non https://bugs.ruby-lang.org/issues/17330#change-90086 * 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}") } ``` ### Possible extensions of the idea It is quite tempting to define the symmetric method named -- as we already have `Object#then` -- `Object#when`: ```ruby some.long.calculation.when { |val| val < 10 } # returns nil if value >= 10 # or even... with support for === some.long.calculation.when(..10)&.then { continue to do something } ``` ...but I am afraid I've overstayed my welcome :) -- https://bugs.ruby-lang.org/ Unsubscribe: