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-ASN: AS4713 221.184.0.0/13 X-Spam-Status: No, score=-3.9 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,MAILING_LIST_MULTI,RCVD_IN_DNSWL_MED, SPF_HELO_NONE,SPF_PASS shortcircuit=no autolearn=ham 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 0D74F1F463 for ; Fri, 27 Dec 2019 20:43:11 +0000 (UTC) Received: from neon.ruby-lang.org (localhost [IPv6:::1]) by neon.ruby-lang.org (Postfix) with ESMTP id 9C66B120B46; Sat, 28 Dec 2019 05:42:58 +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 872BB120B45 for ; Sat, 28 Dec 2019 05:42:56 +0900 (JST) Received: by filterdrecv-p3las1-5bf99c48d-tmd6f with SMTP id filterdrecv-p3las1-5bf99c48d-tmd6f-20-5E066CD4-5C 2019-12-27 20:43:01.047820314 +0000 UTC m=+935835.430049612 Received: from herokuapp.com (unknown [3.90.237.225]) by ismtpd0012p1iad1.sendgrid.net (SG) with ESMTP id 782uCmumSc-5dnUzGZemkw for ; Fri, 27 Dec 2019 20:43:00.945 +0000 (UTC) Date: Fri, 27 Dec 2019 20:43:01 +0000 (UTC) From: daniel@dan42.com Message-ID: References: Mime-Version: 1.0 X-Redmine-MailingListIntegration-Message-Ids: 72185 X-Redmine-Project: ruby-trunk X-Redmine-Issue-Id: 16446 X-Redmine-Issue-Author: sawa X-Redmine-Sender: Dan0042 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?8sy4RigFvRTdBfCVJrT9zb2J88PC92TMQwdNgaWYaq6qCZldaAVDzyVa5+olbh?= =?us-ascii?Q?7CaZrvW0M7S83G03g48JyBpJiKDv1U2QMByiioU?= =?us-ascii?Q?+ExtDiqQQs9ZOLmB026hiGd4SafkV2hN8oHZwUp?= =?us-ascii?Q?vm5oGb7pNJBYBJWVfp8FR14VSyrS+OLtHq1JNki?= =?us-ascii?Q?EqjF+erpc1WLU0epvltGT61rUARI3AxSqVK9lou?= =?us-ascii?Q?zz5yPSRgvKzsPQvB8=3D?= To: ruby-core@ruby-lang.org X-ML-Name: ruby-core X-Mail-Count: 96526 Subject: [ruby-core:96526] [Ruby master Feature#16446] Enumerable#take_*, Enumerable#drop_* counterparts with positive conditions 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 #16446 has been updated by Dan0042 (Daniel DeLorme). I think the positive counterparts of `take_while/drop_while` should be `take_until/drop_until`. And although `take_upto` and `drop_upto` are nicely intuitive and descriptive, I can't think of any good names for their negative counterparts (3) and (4). That may be an indication that we'd be better with a parameter as wishdev suggests. However I find that `:left` and `:right` do not read naturally at all. Maybe a number to indicate how many extra items to take or drop: ```ruby enum.take_while(&:nonzero?) # => [1, 1] enum.drop_while(&:nonzero?) # => [0, 3, 3, 0, 5, 5] enum.take_while(+1, &:nonzero?) # => [1, 1, 0] enum.drop_while(+1, &:nonzero?) # => [3, 3, 0, 5, 5] ``` It's worth noting that there's several other succint ways to accomplish the same thing, so I'm not convinced it's worth adding that many methods. Not everything has to be a one-liner. ```ruby i = ary.index(&:zero?) || ary.size ary[0...i] # => [1, 1] ary[0..i] # => [1, 1, 0] ary[i..-1] # => [0, 3, 3, 0, 5, 5] ary[i+1..-1] # => [3, 3, 0, 5, 5] n = enum.find_index(&:zero?) || enum.count enum.take(n) # => [1, 1] enum.take(n+1) # => [1, 1, 0] enum.drop(n) # => [0, 3, 3, 0, 5, 5] enum.drop(n+1) # => [3, 3, 0, 5, 5] ``` --- BTW you could also classify the three factors as: (1) Whether we want to express the items to include (take) or exclude (drop) (2) Whether we want the left side or the right side in the returned output (3) Whether we want the items to be inclusive or exclusive of the boundary element | |(1)|(2)|(3)|method|example| |--|--|--|--|--|--| |1|take|left |exclusive|take_before|enum.take_before(&:zero?) # => [1, 1] |1|take|left |inclusive|take_upto |enum.take_upto(&:zero?) # => [1, 1, 0] |1|take|right|inclusive|take_from |enum.take_from(&:zero?) # => [0, 3, 3, 0, 5, 5] |1|take|right|exclusive|take_after |enum.take_after(&:zero?) # => [3, 3, 0, 5, 5] |1|drop|left |exclusive|drop_before|enum.drop_before(&:zero?) # => [0, 3, 3, 0, 5, 5] |1|drop|left |inclusive|drop_upto |enum.drop_upto(&:zero?) # => [3, 3, 0, 5, 5] |1|drop|right|inclusive|drop_from |enum.drop_from(&:zero?) # => [1, 1] |1|drop|right|exclusive|drop_after |enum.drop_after(&:zero?) # => [1, 1, 0] This is what I'd call a declarative style, as it describes *what* the method does, not *how*. `take_while` feels more like an imperative style to me since it describes the *how*, the control flow. Ultimately there's so many different ways to think about and express any given operation... there's More Than One Way To Do It but they don't all have to be in the ruby core. ---------------------------------------- Feature #16446: Enumerable#take_*, Enumerable#drop_* counterparts with positive conditions https://bugs.ruby-lang.org/issues/16446#change-83457 * Author: sawa (Tsuyoshi Sawada) * Status: Open * Priority: Normal * Assignee: * Target version: ---------------------------------------- #16441 led me to think about the issue more generally. When we want to split a series of iterations by the first element that satisfies (or dissatisfies) a condition, we have three factors to consider. (1) Whether we want the condition to work **negatively** or **positively** (2) Whether we want the first element to satisfy (or dissatisfy) the condition to be included in the **left** side or the **right** side of the split (3) Whether we want the **left** side or the **right** side in the returned output This leads us to eight possible combinations to consider. ```ruby enum = [1, 1, 0, 3, 3, 0, 5, 5].to_enum ``` | |(1)|(2)|(3)|method|example| |--|--|--|--|--|--| |1|negatively|left|left|`take_while`|`enum.foo1(&:nonzero?) # => [1, 1]`| |2|negatively|left|right|`drop_while`|`enum.foo2(&:nonzero?) # => [0, 3, 3, 0, 5, 5]`| |3|negatively|right|left||`enum.foo3(&:nonzero?) # => [1, 1, 0]`| |4|negatively|right|right||`enum.foo4(&:nonzero?) # => [3, 3, 0, 5, 5]`| |5|positively|left|left||`enum.foo5(&:zero?) # => [1, 1]`| |6|positively|left|right||`enum.foo6(&:zero?) # => [0, 3, 3, 0, 5, 5]`| |7|positively|right|left||`enum.foo7(&:zero?) # => [1, 1, 0]`| |8|positively|right|right||`enum.foo8(&:zero?) # => [3, 3, 0, 5, 5]`| Proposal #16441 asks for a method that corresponds to case 3 in the table above, but I think that would make the paradigm messy unless case 4 is also implemented. Either cases 3 and 4 should both be implemented, or both not. Actually, the current proposal is not about cases 3 and 4. I would leave that to #16641. In many use cases (including the first example in #16641), we want to detect the "marker element" by which we split the iterations. In the cases above, that can be the element `0`. In such use cases, it is more natural to describe the condition in positive terms (i.e., `zero?`) rather than negative terms (i.e., `nonzero?`). (And in other use cases, it might be the other way around.) So I would like to propose methods that correspond to cases 5, 6, 7, 8 above. Naming of the methods should be done systematically. As a candidate, I came up with the following: ||method| |--|--| |5|`take_before`| |6|`drop_before`| |7|`take_upto`| |8|`drop_upto`| -- https://bugs.ruby-lang.org/