[ruby-core:109506] [Ruby master Feature#17330] Object#non
From:
"Dan0042 (Daniel DeLorme)" <noreply@...>
Date:
2022-08-16 17:53:36 UTC
List:
ruby-core #109506
Issue #17330 has been updated by Dan0042 (Daniel DeLorme).
mame (Yusuke Endoh) wrote in #note-15:
> ```
> params[:name]&.non(&:empty?)
> ```
>
> Frankly speaking, this above is dead hard to read and write for me. Do you all really want this?
Personally speaking, yes, the above is clear to me, and reads easily, much more than the alternatives.
> ```
> return Faraday.get(url).non { |r| r.code >= 400 }&.body
> ```
On the other hand I also find this hard to read.
But it's all up to the programmer to write easy-to-read code. This is one of the tools that could help. Or hinder depending on how it's used.
"The **possibility** to make code cryptic itself should not be the reason to withdraw a feature." [-Matz](https://bugs.ruby-lang.org/issues/15723#note-2)
----------------------------------------
Feature #17330: Object#non
https://bugs.ruby-lang.org/issues/17330#change-98675
* 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}") }
```
Basically, the proposal is a "chainable guard clause" that allows to "chain"ify and DRYify code like:
```ruby
value = fetch_something
return value unless value.with_problems?
# which turns into
fetch_something.non(&:with_problems?)
# or
value = fetch_something
value = reasonable_default if value.with_problems?
# turns into
value = fetch_something.non(&:with_problems?) || reasonable_default
```
I believe that this idiom is frequent enough, in combinations like (assorted examples) "read config file but return `nil` if it is empty/wrong version", "fetch latest invoice, but ignore if it has an `unpayable` flag", "fetch a list of last user's searches, but if it is empty, provide default search hints" etc.
I believe there _is_ un unreflected need for idiom like this, the need that is demonstrated by the existence of `nonzero?` and `presence`.
--
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>