Project

General

Profile

Feature #17330

Object#non

Added by zverok (Victor Shepelev) about 2 months ago. Updated 3 days ago.

Status:
Open
Priority:
Normal
Assignee:
-
Target version:
-
[ruby-core:100897]

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:

  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:
class Object
  def non
    self unless yield(self)
  end
end
Usage examples:
  1. 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
    
  2. 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?)
    
  3. 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

Related to Ruby master - Feature #12075: some container#nonempty?Feedbackmatz (Yukihiro Matsumoto)Actions
#1

Updated by nobu (Nobuyoshi Nakada) about 2 months ago

#3

Updated by nobu (Nobuyoshi Nakada) about 2 months ago

  • Description updated (diff)
#4

Updated by nobu (Nobuyoshi Nakada) about 2 months ago

  • Description updated (diff)

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.

Also available in: Atom PDF