From: zverok.offline@...
Date: 2021-01-23T16:41:23+00:00
Subject: [ruby-core:102222] [Ruby master Feature#17330] Object#non

Issue #17330 has been updated by zverok (Victor Shepelev).


@matz Thinking a bit more about it, what I frequently lack is a "singular reject": "drop this value if it doesn't match expectations".

```ruby
# HTTP-get something, don't return result unless successful:
response = Faraday.get(url)
return response unless response.code >= 400
# or worse:
unless response.code >= 400
  return response.body
end

# With non
return Faraday.get(url).non { |r| r.code >= 400 }
# Note how little changes necessary to say "successful response's body"
return Faraday.get(url).non { |r| r.code >= 400 }&.body
```
Though, it seems for me when explaining that one needs a symmetric pair of "singular select"/"singular reject", because particular Faraday examples maybe better written as

```ruby
return Faraday.get(url).when(&:successful?)
# Note how little changes necessary to say "successful response's body"
return Faraday.get(url).when(&:successful?)&.body
```


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

* 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: <mailto:ruby-core-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-core>