[ruby-core:109514] [Ruby master Feature#17330] Object#non
From:
"Dan0042 (Daniel DeLorme)" <noreply@...>
Date:
2022-08-17 14:30:59 UTC
List:
ruby-core #109514
Issue #17330 has been updated by Dan0042 (Daniel DeLorme).
ujihisa (Tatsuhiro Ujihisa) wrote in #note-19:
> `itself_if`
> `itself_unless`
zverok (Victor Shepelev) wrote in #note-21:
> Just `#if` and `#unless` look tempting, though.
Interesting suggestion. Let's look at the previous "non" examples rewritten with "itself_unless" and "unless"
```ruby
calculate.some.limit.non(&:zero?) || DEFAULT_LIMIT
name = params[:name]&.non(&:empty?)
payload.dig('action', 'type').non { |action| PROHIBITED_ACTIONS.include?(action) }
fetch_something.non(&:with_problems?)
return Faraday.get(url).non { |r| r.code >= 400 }&.body
if user.subscription&.non(&:expired?)
calculate.some.limit.itself_unless(&:zero?) || DEFAULT_LIMIT
name = params[:name]&.itself_unless(&:empty?)
payload.dig('action', 'type').itself_unless { |action| PROHIBITED_ACTIONS.include?(action) }
fetch_something.itself_unless(&:with_problems?)
return Faraday.get(url).itself_unless { |r| r.code >= 400 }&.body
if user.subscription&.itself_unless(&:expired?)
calculate.some.limit.unless(&:zero?) || DEFAULT_LIMIT
name = params[:name]&.unless(&:empty?)
payload.dig('action', 'type').unless { |action| PROHIBITED_ACTIONS.include?(action) }
fetch_something.unless(&:with_problems?)
return Faraday.get(url).unless { |r| r.code >= 400 }&.body
if user.subscription&.unless(&:expired?)
```
YMMV, but to my eye it looks like "non" is more expressive when used with a single predicate method, and "itself_unless" is more readable for longer blocks. And "unless" is remarkably readable in both situations.
> I wonder if `itself_unless` is actually useful rather than confusing.
> You can easily rewrite `itself_unless` with `itself_if` with `!`, such as `x.itself_if { !_1.pred? }` which is pretty straightforward.
I think it's more useful than `itself_if`. In many cases the negation itself is what makes this useful, otherwise you would just use the predicate directly.
```ruby
if user.subscription&.active? # if subscription has "active?" there is no need here
if user.subscription&.itself_if(&:active?) # to use "oui" or "itself_if"
if user.subscription&.non(&:expired?) # if subscription only has "expired?" it needs to be negated,
if user.subscription&.itself_if{ !_1.expired? } # which is why we need "non" or a negated "itself_if" or
if user.subscription&.itself_unless(&:expired?) # "itself_unless"
```
----------------------------------------
Feature #17330: Object#non
https://bugs.ruby-lang.org/issues/17330#change-98683
* 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>