[ruby-core:102225] [Ruby master Feature#17330] Object#non
From:
zverok.offline@...
Date:
2021-01-24 09:31:07 UTC
List:
ruby-core #102225
Issue #17330 has been updated by zverok (Victor Shepelev).
@nobu
> This seems readable enough and more flexible.
```ruby
return Faraday.get(url).then {_1.body if _1.successful?}
```
I argue that the idiom (return something/continue with something, conditionally) is so frequent, that it should be more atomic.
> Or, make `successful?` to return `self` or `nil`
That's what I started this ticket with!
* This is not natural for Ruby: `nonzero?` is frequently mocked and generally considered a "design error"; Rails' `presence` has kind of the same vibe
* This requires me to change Faraday (which, to be clear, I have no relation to), and then go and change every library which causes the same idiom :)
* This is solving very particular case (just like `nonzero?` and `presence`), I am talking about generic.
Here's one more example:
```ruby
Faraday.get(url).body.then { JSON.parse(_1, symbolize_names: true) }.non { _1.key?('error') }&.dig('user', 'status')
```
----------------------------------------
Feature #17330: Object#non
https://bugs.ruby-lang.org/issues/17330#change-90075
* 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>