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.7 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 2314C1F4B4 for ; Tue, 29 Sep 2020 14:35:44 +0000 (UTC) Received: from neon.ruby-lang.org (localhost [IPv6:::1]) by neon.ruby-lang.org (Postfix) with ESMTP id 3FCF0120A7B; Tue, 29 Sep 2020 23:35:03 +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 4966E120937 for ; Tue, 29 Sep 2020 23:35:00 +0900 (JST) Received: by filterdrecv-p3mdw1-5dd6bc5999-hr9dm with SMTP id filterdrecv-p3mdw1-5dd6bc5999-hr9dm-18-5F734635-C 2020-09-29 14:35:33.098462977 +0000 UTC m=+664618.612541133 Received: from herokuapp.com (unknown) by ismtpd0050p1mdw1.sendgrid.net (SG) with ESMTP id ypGxtBTiR-WRDret41sN_A for ; Tue, 29 Sep 2020 14:35:33.041 +0000 (UTC) Date: Tue, 29 Sep 2020 14:35:33 +0000 (UTC) From: esquinas.enrique@gmail.com Message-ID: References: Mime-Version: 1.0 X-Redmine-MailingListIntegration-Message-Ids: 76056 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: esquinas 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?9oBaWJTnYgP+fjkhJygwvhlAQIhDEjpDvelFMbg=2FXFs9enQxtjfLWydhz7OWaK?= =?us-ascii?Q?LQkram6Ukjuz+TmckD9KmhMewDAfARlOjOl2FlV?= =?us-ascii?Q?TOUoBb=2FdQZy1tRx9hZEDLc2f81CEwLYXOR8V432?= =?us-ascii?Q?oxNbsXllT4M6f4iB4hwN2uVugfXvMa+=2FA8PMx3W?= =?us-ascii?Q?w61u2OijD=2F75875Pk2K9KWp1t8CC9leyEUJXvHY?= =?us-ascii?Q?GNjoXFYKlasU9E=2FdA=3D?= To: ruby-core@ruby-lang.org X-ML-Name: ruby-core X-Mail-Count: 100215 Subject: [ruby-core:100215] [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="iso-8859-1" Content-Transfer-Encoding: quoted-printable Errors-To: ruby-core-bounces@ruby-lang.org Sender: "ruby-core" Issue #16986 has been updated by esquinas (Enrique Esquinas). duerst (Martin D=FCrst) wrote in #note-43: > One more point: I haven't seen much examples of similar features in other= languages. The only suggestion I saw was that of a similarity to Python tu= ples. But tuples are much closer to Arrays than to Structs or hashes. The e= asiest description for them may be "fixed-length Arrays". > = > (While not being available in (m)any other language(s) isn't by itself an= argument against a feature, it definitely strengthens the need for careful= evaluation and explanation of a new feature, including actual practical us= e cases.) Apart from Structs, examples have been given: regular Classes, Hashes, Open= Structs, and indirectly through the reference to this related Issue #16122 = about using Struct::Value as native Value objects. I would also add the con= venience of Javascript objects as "value objects" or quick duck-typed mocks= , for testing, refactoring or other purposes: ``` javascript // Original Point class may be complex but in JS we can do this instead of = instantiating Point: let point =3D { x: 12, y: 34, z: 56 }; console.log(calculationOn3D(point)); ``` Here `calculationOn3D` expects the `point` variable to implement the 3D int= erface and be accessed like `point.x`,`point.y`,`point.z` so it just works. In Ruby we currently have the option to `Struct.new(:x, :y, :z).new(12, 34,= 56)` or use `OpenStruct.new({ x: 12, y: 34, z; 56 })` which we have to req= uire and has other problems already addressed at the top by @Ko1 It's my understanding that we want a feature to easily, quickly, efficientl= y, natively, **naturally** create a *"value object"-like* intance so we cou= ld do something like: ``` ruby point =3D %struct{ x: 12, y: 34, z: 56 } # ^1 puts "Our point is #{point.z} units deep" puts calculation_on_3D(point) # ... ``` I hope this issue doesn't start to go in circles and lose focus. However, t= here's a discussion to have and find whether using `%struct` is a good choi= ce or not. Specially when, at the end, the instance created by the literal = notation `%struct{...}` may not resemble a regular struct very much... Or `= %struct{...}` may be more than perfect if will be strictly equivalent to `S= truct.new(:x, :y, :z).new(12, 34, 56)`. **^1** One of the new syntaxes proposed by @ko1 ---------------------------------------- Feature #16986: Anonymous Struct literal https://bugs.ruby-lang.org/issues/16986#change-87805 * 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 =3D {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 =3D Struct.new(:name, :age)` = and `person =3D Person.new('ko1', 'Japan')`, we can access its values throu= gh `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 =3D 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 s= everal 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 =3D ${a: 1, b: 2, c: 3} s2 =3D ${a: 1, b: 2, c: 3} assert s1 =3D=3D s2 s3 =3D ${a: 1, c: 3, b: 2} s4 =3D ${d: 4} assert_equal false, s1 =3D=3D s3 assert_equal false, s1 =3D=3D s4 ``` ## Note Unlike Hash literal syntax, this proposal only allows `label: expr` notatio= n. No `${**h}` syntax. This is because if we allow to splat a Hash, it can be a vulnerability by s= platting outer-input Hash. Thanks to this spec, we can specify anonymous Struct classes at compile tim= e. 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 =3D Struct.new(:a, :b).new(1, 2) hs =3D {a: 1, b: 2} class C attr_reader :a, :b def initialize() =3D (@a =3D 1; @b =3D 2) end ob =3D 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.25= ns/i, 76clocks/i) hs[:a] 37.845M i/s - 38.037M times in 1.005051s (26.42= ns/i, 76clocks/i) st.a 33.348M i/s - 33.612M times in 1.007904s (29.99= ns/i, 87clocks/i) Calculating ------------------------------------- ob.a 87.917M i/s - 114.300M times in 1.300085s (11.37= ns/i, 33clocks/i) hs[:a] 85.504M i/s - 113.536M times in 1.327850s (11.70= ns/i, 33clocks/i) st.a 61.337M i/s - 100.045M times in 1.631064s (16.30= ns/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 im= prove 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 nam= e. ```ruby s1 =3D ${a:1} s2 =3D ${a:2} p [s1, s2] #=3D> [#, #] A =3D s1.class p [s1, s2] #=3D> [#, #] ``` Maybe that is not a good behavior. -- = https://bugs.ruby-lang.org/ Unsubscribe: