Feature #17330
Object#non
Description
(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:
nonzero?
in Ruby core (frequently mocked for "inadequate" behavior, as it is looking like predicate method, but instead oftrue
/false
returns an original value ornil
)- ActiveSupport
Object#presence
, which also returns an original value ornil
if it is not "present" (e.g.nil
orempty?
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:¶
class Object
def non
self unless yield(self)
end
end
Usage examples:¶
With number:
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
With string:
name = params[:name] if params[:name] && !params[:name].empty? # or, with ActiveSupport: name = params[:name].presence # with non: name = params[:name]&.non(&:empty?)
More complicated example
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
:
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 :)
Related issues
Updated by nobu (Nobuyoshi Nakada) about 2 months ago
- Related to Feature #12075: some container#nonempty? added
Updated by nobu (Nobuyoshi Nakada) about 2 months ago
It reminded me https://bugs.ruby-lang.org/issues/12075#change-57152.
Updated by sawa (Tsuyoshi Sawada) 14 days ago
I think the proposed feature would be useful, but I feel that your focus on use cases with a negative predicate is artificial. Positive predicates should have as many corresponding use cases. And since negation is always one step more complex than its positive counterpart, you should first (or simultaneously) propose the positive version, say Object#oui
:
calculate.some.limit.oui(&:nonzero?) || DEFAULT_LIMIT
params[:name]&.oui{ _1.empty?.! }
payload.dig('action', 'type').oui{ PROHIBITED_ACTIONS.include?(_1).! }
I think such feature has actually been proposed in the past.
Furthermore, I suggest you may also propose the method(s) to take a variable numbers of arguments to match against:
payload.dig('action', 'type').non(*PROHIBITED_ACTIONS)
And by "match", I think that using the predicate ===
would be more useful than ==
:
"foo".non(/\AError: /, /\AOops, /) # => "foo"
"Oops, something went wrong.".non(/\AError: /, /\AOops, /) # => nil
Updated by akr (Akira Tanaka) 4 days ago
I prefer "not" method than this "non" method.
x.empty?.not
Although x.empty?.!
is possible now, !
method is not very readable.
Updated by matz (Yukihiro Matsumoto) 3 days ago
I don't see the non
method make code more readable by glancing at the examples.
Can you elaborate on the benefit of the proposal?
Matz.