ruby-core@ruby-lang.org archive (unofficial mirror)
 help / color / mirror / Atom feed
From: sawadatsuyoshi@gmail.com
To: ruby-core@ruby-lang.org
Subject: [ruby-core:101876] [Ruby master Feature#17330] Object#non
Date: Sat, 02 Jan 2021 09:46:59 +0000 (UTC)	[thread overview]
Message-ID: <redmine.journal-89717.20210102094658.710@ruby-lang.org> (raw)
In-Reply-To: redmine.issue-17330.20201117124902.710@ruby-lang.org

Issue #17330 has been updated by sawa (Tsuyoshi Sawada).


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`:

```ruby
calculate.some.limit.oui(&:nonzero?) || DEFAULT_LIMIT

params[:name]&.oui{ _1.empty?.! }

payload.dig('action', 'type').oui{ PROHIBITED_ACTIONS.include?(_1).! }
```

Furthermore, I suggest you may also propose the method(s) to take a variable numbers of arguments to match against:

```ruby
payload.dig('action', 'type').non(*PROHIBITED_ACTIONS)
```

And by "match", I think that using the predicate `===` would be more useful than `==`:


```ruby
"foo".non(/\AError: /, /\AOops, /) # => "foo"
"Oops, something went wrong.".non(/\AError: /, /\AOops, /) # => nil
```



----------------------------------------
Feature #17330: Object#non
https://bugs.ruby-lang.org/issues/17330#change-89717

* 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/

  parent reply	other threads:[~2021-01-02  9:47 UTC|newest]

Thread overview: 21+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-11-17 12:49 [ruby-core:100897] [Ruby master Feature#17330] Object#non zverok.offline
2020-11-17 12:55 ` [ruby-core:100898] " nobu
2021-01-02  9:46 ` sawadatsuyoshi [this message]
2021-01-12  5:40 ` [ruby-core:102017] " akr
2021-01-13  7:07 ` [ruby-core:102054] " matz
2021-01-23 16:41 ` [ruby-core:102222] " zverok.offline
2021-01-24  2:42 ` [ruby-core:102223] " nobu
2021-01-24  9:31 ` [ruby-core:102225] " zverok.offline
2021-01-25 15:54 ` [ruby-core:102236] " daniel
2022-08-08  7:51 ` [ruby-core:109446] " byroot (Jean Boussier)
2022-08-10 17:48 ` [ruby-core:109467] " Dan0042 (Daniel DeLorme)
2022-08-16 15:02 ` [ruby-core:109497] " mame (Yusuke Endoh)
2022-08-16 15:23 ` [ruby-core:109501] " zverok (Victor Shepelev)
2022-08-16 16:19 ` [ruby-core:109504] " mame (Yusuke Endoh)
2022-08-16 17:53 ` [ruby-core:109506] " Dan0042 (Daniel DeLorme)
2022-08-17  3:23 ` [ruby-core:109507] " ujihisa (Tatsuhiro Ujihisa)
2022-08-17  4:46 ` [ruby-core:109508] " sawa (Tsuyoshi Sawada)
2022-08-17  5:13 ` [ruby-core:109509] " zverok (Victor Shepelev)
2022-08-17  5:36 ` [ruby-core:109510] " hmdne (hmdne -)
2022-08-17 14:30 ` [ruby-core:109514] " Dan0042 (Daniel DeLorme)
2022-08-19  5:09 ` [ruby-core:109565] " matz (Yukihiro Matsumoto)

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-list from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://www.ruby-lang.org/en/community/mailing-lists/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=redmine.journal-89717.20210102094658.710@ruby-lang.org \
    --to=ruby-core@ruby-lang.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).