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=-3.8 required=3.0 tests=AWL,BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,MAILING_LIST_MULTI,RCVD_IN_DNSWL_MED, SPF_HELO_NONE,SPF_PASS,UNPARSEABLE_RELAY 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 9323E1F4B4 for ; Thu, 15 Oct 2020 17:40:25 +0000 (UTC) Received: from neon.ruby-lang.org (localhost [IPv6:::1]) by neon.ruby-lang.org (Postfix) with ESMTP id 349BE1209B1; Fri, 16 Oct 2020 02:39:44 +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 AFAAB1209A6 for ; Fri, 16 Oct 2020 02:39:41 +0900 (JST) Received: by filterdrecv-p3mdw1-76f79d8b59-6h4h8 with SMTP id filterdrecv-p3mdw1-76f79d8b59-6h4h8-19-5F88897C-34 2020-10-15 17:40:12.315237935 +0000 UTC m=+237460.220026923 Received: from herokuapp.com (unknown) by ismtpd0077p1mdw1.sendgrid.net (SG) with ESMTP id U4gO4vB-RVOECpBX2QcdRw for ; Thu, 15 Oct 2020 17:40:12.165 +0000 (UTC) Date: Thu, 15 Oct 2020 17:40:12 +0000 (UTC) From: ko1@atdot.net Message-ID: References: Mime-Version: 1.0 X-Redmine-MailingListIntegration-Message-Ids: 76272 X-Redmine-Project: ruby-master X-Redmine-Issue-Tracker: Feature X-Redmine-Issue-Id: 16986 X-Redmine-Issue-Author: ko1 X-Redmine-Issue-Assignee: matz X-Redmine-Sender: ko1 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?fVTMYOBjtdvXNcWwrscBhLsHItUXVK5L4mtnq0mdcReXpor5aKE7dXbt3g5OUT?= =?us-ascii?Q?5m+=2FLBWLTqeb=2Fm5lv3JEUHfWtFLOepbDlL8YNzq?= =?us-ascii?Q?gG6agi9o9F=2F7Na2hw0D0=2FYoZMi14rNannvgVywJ?= =?us-ascii?Q?l4FXTvBgyD6zvz+jiwDxKFRyhFMZmYxWBpxqNF8?= =?us-ascii?Q?jLlgFeTWcTIOgg5UoGSX1pLpWeHcytebrKw=3D=3D?= To: ruby-core@ruby-lang.org X-Entity-ID: b/2+PoftWZ6GuOu3b0IycA== X-ML-Name: ruby-core X-Mail-Count: 100407 Subject: [ruby-core:100407] [Ruby master Feature#16986] Anonymous Struct literal 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 #16986 has been updated by ko1 (Koichi Sasada). marcandre (Marc-Andre Lafortune) wrote in #note-55: > Seems to me that `s = {a: 0, b: 0}` is a good solution here. If you mistype `aa`, you *want* an error, so I don't see the issue. In this case, yes. So IDE support in future? (cleaver IDE seems to inspect the hash keys, though...) > FWIW, it's not clear to me why you don't simply use 2 variables. A set of values is easy to pass to other methods and so on (`pp s` on this example). > The question is if it's a compelling use, if it significantly improves our programming life. I believe this usecase improves my life :p At least if I can say it is a fixed set of values (only a and b) is a good reason. Also writing `s.a` and `s.b` make me feeling good, than writing `s[:a]` and `s[;b]`. "feeling good" is enough for me, and I believe stacking "feeling good" is a history of Ruby. Of course, if disadvantage is bigger than "feeling good", we should not introduce the feature. In this case, disadvantage is introducing new syntax will increase the language spec and complexity. Also break the compatibility with Ruby 2.7 and earlier. Introducing new method such as `Struct(a: 1, b: 2)` doesn't have an issues because of new syntax, but is able to becomes vulnerability. implementation technique (for example, using `method_missing` like ostruct) can solve. No time to implement it before 3.0 release for me. A method approach can reduce performance improvement chance, but it depends implementation technique. I think there are other cases we can utilize it. Maybe it is not used on (well-maintained) library interface because they can define a struct or a class. ---- BTW I don't think it is good way to say "significantly improves our programming life" is required to introduce/discuss about new feature. For example, I don't think making Set literal doesn't improve our programming life significantly because I never use Set (I use Hash). But I think I may change my mind when I use it for some best fit case. ---------------------------------------- Feature #16986: Anonymous Struct literal https://bugs.ruby-lang.org/issues/16986#change-88020 * Author: ko1 (Koichi Sasada) * Status: Open * Priority: Normal * Assignee: matz (Yukihiro Matsumoto) ---------------------------------------- # Abstract How about introducing anonymous Struct literal such as `${a: 1, b: 2}`? It is almost the same as `Struct.new(:a, :b).new(1, 2)`. # Proposal ## Background In many cases, people use hash objects to represent a set of values such as `person = {name: "ko1", country: 'Japan'}` and access its values through `person[:name]` and so on. It is not easy to write (three characters `[:]`!), and it easily introduces misspelling (`person[:nama]` doesn't raise an error). If we make a `Struct` object by doing `Person = Struct.new(:name, :age)` and `person = Person.new('ko1', 'Japan')`, we can access its values through `person.name` naturally. However, it costs coding. And in some cases, we don't want to name the class (such as `Person`). Using `OpenStruct` (`person = OpenStruct.new(name: "ko1", country: "Japan")`), we can access it through `person.name`, but we can extend the fields unintentionally, and the performance is not good. Of course, we can define a class `Person` with attr_readers. But it takes several lines. To summarize the needs: * Easy to write * Doesn't require declaring the class * Accessible through `person.name` format * Limited fields * Better performance ## Idea Introduce new literal syntax for an anonymous Struct such as: `${ a: 1, b: 2 }`. Similar to Hash syntax (with labels), but with `$` prefix to distinguish. Anonymous structs which have the same member in the same order share their class. ```ruby s1 = ${a: 1, b: 2, c: 3} s2 = ${a: 1, b: 2, c: 3} assert s1 == s2 s3 = ${a: 1, c: 3, b: 2} s4 = ${d: 4} assert_equal false, s1 == s3 assert_equal false, s1 == s4 ``` ## Note Unlike Hash literal syntax, this proposal only allows `label: expr` notation. No `${**h}` syntax. This is because if we allow to splat a Hash, it can be a vulnerability by splatting outer-input Hash. Thanks to this spec, we can specify anonymous Struct classes at compile time. We don't need to find or create Struct classes at runtime. ## Implementatation https://github.com/ruby/ruby/pull/3259 # Discussion ## Notation Matz said he thought about `{|a: 1, b: 2 |}` syntax. ## Performance Surprisingly, Hash is fast and Struct is slow. ```ruby Benchmark.driver do |r| r.prelude <<~PRELUDE st = Struct.new(:a, :b).new(1, 2) hs = {a: 1, b: 2} class C attr_reader :a, :b def initialize() = (@a = 1; @b = 2) end ob = C.new PRELUDE r.report "ob.a" r.report "hs[:a]" r.report "st.a" end __END__ Warming up -------------------------------------- ob.a 38.100M i/s - 38.142M times in 1.001101s (26.25ns/i, 76clocks/i) hs[:a] 37.845M i/s - 38.037M times in 1.005051s (26.42ns/i, 76clocks/i) st.a 33.348M i/s - 33.612M times in 1.007904s (29.99ns/i, 87clocks/i) Calculating ------------------------------------- ob.a 87.917M i/s - 114.300M times in 1.300085s (11.37ns/i, 33clocks/i) hs[:a] 85.504M i/s - 113.536M times in 1.327850s (11.70ns/i, 33clocks/i) st.a 61.337M i/s - 100.045M times in 1.631064s (16.30ns/i, 47clocks/i) Comparison: ob.a: 87917391.4 i/s hs[:a]: 85503703.6 i/s - 1.03x slower st.a: 61337463.3 i/s - 1.43x slower ``` I believe we can speed up `Struct` similarly to ivar accesses, so we can improve the performance. BTW, OpenStruct (os.a) is slow. ``` Comparison: hs[:a]: 92835317.7 i/s ob.a: 85865849.5 i/s - 1.08x slower st.a: 53480417.5 i/s - 1.74x slower os.a: 12541267.7 i/s - 7.40x slower ``` For memory consumption, `Struct` is more lightweight because we don't need to keep the key names. ## Naming If we name an anonymous class, literals with the same members share the name. ```ruby s1 = ${a:1} s2 = ${a:2} p [s1, s2] #=> [#, #] A = s1.class p [s1, s2] #=> [#, #] ``` Maybe that is not a good behavior. -- https://bugs.ruby-lang.org/