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=-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 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 2B16C1F463 for ; Sat, 28 Dec 2019 14:02:22 +0000 (UTC) Received: from neon.ruby-lang.org (localhost [IPv6:::1]) by neon.ruby-lang.org (Postfix) with ESMTP id 9A588120ABE; Sat, 28 Dec 2019 23:02:07 +0900 (JST) Received: from o1678948x4.outbound-mail.sendgrid.net (o1678948x4.outbound-mail.sendgrid.net [167.89.48.4]) by neon.ruby-lang.org (Postfix) with ESMTPS id 66C78120AB6 for ; Sat, 28 Dec 2019 23:02:04 +0900 (JST) Received: by filterdrecv-p3las1-5bf99c48d-stcpj with SMTP id filterdrecv-p3las1-5bf99c48d-stcpj-19-5E07605F-89 2019-12-28 14:02:07.90059902 +0000 UTC m=+998187.285927992 Received: from herokuapp.com (unknown [3.82.152.27]) by ismtpd0026p1iad2.sendgrid.net (SG) with ESMTP id A9spUlG8TlOPaW4U7EJG2w for ; Sat, 28 Dec 2019 14:02:07.633 +0000 (UTC) Date: Sat, 28 Dec 2019 14:02:07 +0000 (UTC) From: zverok.offline@gmail.com Message-ID: References: Mime-Version: 1.0 X-Redmine-MailingListIntegration-Message-Ids: 72211 X-Redmine-Project: ruby-trunk X-Redmine-Issue-Id: 16435 X-Redmine-Issue-Author: zverok X-Redmine-Sender: zverok 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?3be0g8093pjUjT94eiCA64csFDBI=2FmHQTWm54P5gda5iTux4E6x8WRAO220sk1?= =?us-ascii?Q?eZmRQXN4wQePjBgpG46XdkdyMyvcrTvWbm0mXqW?= =?us-ascii?Q?OmRI51A+ThxgcZS6sR8nD5soyGUH8chj8sP1rxP?= =?us-ascii?Q?o9uRtT+PIVpw+=2Fvir4sZijhbho=2FlVZsjuAagpos?= =?us-ascii?Q?LqYeADamDFlCxE=2FtRY5=2Fns4aoSjDY5Ekxe869Sl?= =?us-ascii?Q?=2FcD2ESWslhtD0eD4c=3D?= To: ruby-core@ruby-lang.org X-ML-Name: ruby-core X-Mail-Count: 96552 Subject: [ruby-core:96552] [Ruby master Feature#16435] Array#to_proc 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 #16435 has been updated by zverok (Victor Shepelev). @Dan0042 well, it could be a "Stockholm syndrome" on my side, but I tend to believe thinking "I want something `to_proc`-able here" leads me to structure code more clearly, and understand "what should be where". (I already expressed this thought in several forms while arguing for "method reference" operator and explaining why I dislike implicit block args.) Like, generically, when I have somewhere... ```ruby [list, of, some, objects].select { |o| check some condition (o) }.map { |o| some mapping (o) } ``` ...and I want to write it prettier and DRYer, the first thing I am thinking is "shouldn't it be a part of `o`'s interface?" (so I can do `select(&:condition?)`), and then "shouldn't it be some concept in the current scope?" (so I can do at least `select(&method(:condition?))`). My _practical experience_ and _intuition_ (though, it is hard to argue for or against either of them with a simple and unambiguous examples) say that typically it makes code much cleaner, better separated and better testable. So, it makes me super-sad that now every discussion of "how we can have atomic objects" meets with "just use numbered parameters if you want to spare some characters". No, I don't, I can type faster than I think. I want to spare some _concepts_, to think less, and to think more clear. One absolutely hypothetical example: if you have `max_by{_1[:salary]}`, and then somebody says "oh, but exclude management from the calculation", it is all to easy to just `max_by{_1[:role] == :management ? 0 : _1[:salary]}`, and continue this way till you have very short, very unreadable, very convoluted block of logic. While with `max_by(&[:salary])` you'll stop for a second... And maybe filter out `role=management` earlier, or question the whole logic, or something else. (But I am aware that typically I am arguing _against_ "limitations what you can write, so you'll think better" in Ruby :)) One additional theoretical consideration is: if once we'll have some cool Ruby optimizer, "atomic" statements should be easier analyzable and optimizable (inlineable or something) than "blocks with almost the same number of characters" but several different statemnts. ---------------------------------------- Feature #16435: Array#to_proc https://bugs.ruby-lang.org/issues/16435#change-83483 * Author: zverok (Victor Shepelev) * Status: Open * Priority: Normal * Assignee: * Target version: ---------------------------------------- The idea is obvious, but I couldn't find it discussed anywhere on tracker before. Please point me at the previous discussions if any. ```ruby class Array def to_proc proc { |v| v.dig(*self) } end end # Or, alternatively, see about alternatives at the end of proposal: class Array def to_proc proc { |v| v[*self] } end end ``` The implementation seems to provide clean and unambiguous collections indexing in Enumerators: ```ruby # Basic objects data, which could be obtained from JSON, CSV, Database... data = [ {name: 'John', department: {id: 1, title: 'Engineering'}, salary: 1000}, {name: 'Jane', department: {id: 1, title: 'Engineering'}, salary: 1200}, {name: 'Boris', department: {id: 2, title: 'Accounting'}, salary: 800}, {name: 'Alice', department: {id: 3, title: 'Management'}, salary: 1500} ] data.map(&[:name]) # => ["John", "Jane", "Boris", "Alice"] data.min_by(&[:salary]) # => {:name=>"Boris", :department=>{:id=>2, :title=>"Accounting"}, :salary=>800} pp data.group_by(&[:department, :title]) # {"Engineering"=> # [{:name=>"John", # :department=>{:id=>1, :title=>"Engineering"}, # :salary=>1000}, # {:name=>"Jane", # :department=>{:id=>1, :title=>"Engineering"}, # :salary=>1200}], # "Accounting"=> # [{:name=>"Boris", # :department=>{:id=>2, :title=>"Accounting"}, # :salary=>800}], # "Management"=> # [{:name=>"Alice", # :department=>{:id=>3, :title=>"Management"}, # :salary=>1500}]} # Works with arrays, too: data.map(&:values).map(&[0]) # => ["John", "Jane", "Boris", "Alice"] # And with mixes: data.group_by(&[:department, :title]).values.map(&[0, :name]) # => ["John", "Boris", "Alice"] ``` Naked structured data seems to be a common enough thing to make working with them easier. Some prior info: * Googling it around, I found the idea was first invented [back in 2014](https://thepugautomatic.com/2014/11/array-to-proc-for-hash-access/), and another one [in 2015](https://gist.github.com/geowy/39fde25ec2966f90a54b), not sure if it was proposed on the tracker. * Other proposals for `Array#to_proc` was: to call several methods in sequence [1](http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/199820), [2](https://rails.lighthouseapp.com/projects/8994/tickets/1253-arrayto_proc), and to call method with argument [1](https://www.sanityinc.com/articles/adding-array-to-proc-to-ruby/), [2](https://bugs.ruby-lang.org/issues/10829), [3](https://www.rubydoc.info/github/estum/console_utils/Array:to_proc), to call several methods in parallel: [1](https://gist.github.com/shell/1120249) Honestly, I feel that proposed usage is the most frequently needed. Also, the readability of the version seems more or less straightforward: ```ruby # Existing shortcut, for example: data.map(&:keys) # Is equivalent to data.map { |x| x.keys } # ^^^^^ -- "just remove this part" # Proposed shortcut: data.map(&[:name]) # Is equivalent to data.map { |x| x[:name] } # ^^^^^ -- "just remove this part" ``` **`dig` or `[]` alternative implementations** It is up to discussion (if the whole idea holds water) whether `dig` should be used or just `[]`. The `dig` version is convenient for nested structures but slightly breaks "equivalency" shown above, and just `[]` version will allow this: ```ruby data.map(&:values).map(&[1..-1]) # => [[{:id=>1, :title=>"Engineering"}, 1000], [{:id=>1, :title=>"Engineering"}, 1200], [{:id=>2, :title=>"Accounting"}, 800], [{:id=>3, :title=>"Management"}, 1500]] ``` Maybe, for the sake of explainability, "just `[]`" should be preferred, with digging performed by other means. -- https://bugs.ruby-lang.org/