From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on dcvr.yhbt.net X-Spam-Level: X-Spam-Status: No, score=-2.6 required=3.0 tests=AWL,BAYES_00, DKIM_ADSP_CUSTOM_MED,FORGED_GMAIL_RCVD,FREEMAIL_FORGED_FROMDOMAIN, FREEMAIL_FROM,HEADER_FROM_DIFFERENT_DOMAINS,MAILING_LIST_MULTI, RCVD_IN_DNSWL_MED,SPF_HELO_NONE,SPF_PASS,UNPARSEABLE_RELAY shortcircuit=no autolearn=no autolearn_force=no version=3.4.2 Received: from neon.ruby-lang.org (neon.ruby-lang.org [221.186.184.75]) by dcvr.yhbt.net (Postfix) with ESMTP id 555D61F4B4 for ; Sat, 2 Jan 2021 09:47:07 +0000 (UTC) Received: from neon.ruby-lang.org (localhost [IPv6:::1]) by neon.ruby-lang.org (Postfix) with ESMTP id 1A7CC120AB9; Sat, 2 Jan 2021 18:46:17 +0900 (JST) Received: from xtrwkhkc.outbound-mail.sendgrid.net (xtrwkhkc.outbound-mail.sendgrid.net [167.89.16.28]) by neon.ruby-lang.org (Postfix) with ESMTPS id 62FEE120A79 for ; Sat, 2 Jan 2021 18:46:14 +0900 (JST) Received: by filterdrecv-p3las1-685fdc5bbc-t824x with SMTP id filterdrecv-p3las1-685fdc5bbc-t824x-19-5FF04113-13 2021-01-02 09:46:59.738691474 +0000 UTC m=+1939482.886973646 Received: from herokuapp.com (unknown) by ismtpd0088p1mdw1.sendgrid.net (SG) with ESMTP id ZgXVyXrARo6PsOmvD_-32g for ; Sat, 02 Jan 2021 09:46:59.612 +0000 (UTC) Date: Sat, 02 Jan 2021 09:46:59 +0000 (UTC) From: sawadatsuyoshi@gmail.com Message-ID: References: Mime-Version: 1.0 X-Redmine-MailingListIntegration-Message-Ids: 77782 X-Redmine-Project: ruby-master X-Redmine-Issue-Tracker: Feature X-Redmine-Issue-Id: 17330 X-Redmine-Issue-Author: zverok X-Redmine-Sender: sawa X-Mailer: Redmine X-Redmine-Host: bugs.ruby-lang.org X-Redmine-Site: Ruby Issue Tracking System X-Auto-Response-Suppress: All Auto-Submitted: auto-generated X-SG-EID: =?us-ascii?Q?jFXA8Rt481sXUUIO9tYW1AJlMOZdNdlSw=2F5TfLCefGuRVw6WBj2VRlblFMxYqE?= =?us-ascii?Q?=2F51dFhe44lD9Wfu29tU9D52ClA=2FFowBQsltP+Fz?= =?us-ascii?Q?QViANWdaK9x7oOziPysNDgvQXAhQijPygJNs47N?= =?us-ascii?Q?3VX0+LICTMYo6UrRJvDT8=2FUAbptUPfjY1aG4TfA?= =?us-ascii?Q?+rOFu2scaag59eelRD3s1nl01BjJHi0eehyeJLd?= =?us-ascii?Q?9VZDWU+sf6arAase4=3D?= To: ruby-core@ruby-lang.org X-Entity-ID: b/2+PoftWZ6GuOu3b0IycA== X-ML-Name: ruby-core X-Mail-Count: 101876 Subject: [ruby-core:101876] [Ruby master Feature#17330] Object#non X-BeenThere: ruby-core@ruby-lang.org X-Mailman-Version: 2.1.15 Precedence: list Reply-To: Ruby developers List-Id: Ruby developers List-Unsubscribe: , List-Post: List-Help: List-Subscribe: , Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: ruby-core-bounces@ruby-lang.org Sender: "ruby-core" 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/