From: shevegen@... Date: 2020-06-26T09:36:31+00:00 Subject: [ruby-core:98949] [Ruby master Feature#16986] Anonymous Struct literal Issue #16986 has been updated by shevegen (Robert A. Heiler). First, I like the idea, so +1 for the idea. It also reminds me of a more "prototypic-based approach" in general with structs, so if the syntax could be made simpler to type, that seems to be a possible improvement. And it fits into other "shortcut" variants, such as %w() %i() and so forth. I make use of %w() and so forth a LOT - it really helps when you have to create longer arrays; we can avoid all the middle "','" parts. What I dislike a bit about the suggestion here is the proposed syntax. There are two aspects as to why that is the case for me: (1) While I do sometimes use $ variables (in particular the regex variants $1 $2 are so convenient to use), sometimes I use $stdin as well, but I don't quite like global variables in general. In particular remembering $: $? $_ (if these even exist) is a bit tedious. So most of the time when we can avoid $, I think this is better than having to use $. But this is just one part. (2) The other, perhaps slightly more problematic thing, is that we would introduce a new variant of how people have to understand $ variables. Specifically this variant: ${a: 1, b: 2} May look like regular variable substitution such as: cat = 'Tom' puts "#{cat} is hunting Jerry." Possibly the {} may have ruby user associate this with a regular substitution. The other aspect is that this would be the first global variable use that combines {} upon "definition" time. We used to have something like this: $abc = 'def' That feels a bit different to: abc = ${a: 1, b: 2} Hmm. > Matz said he thought about {|a: 1, b: 2 |} syntax. Do you mean without '$' there? If so then I think the syntax by matz is better. Though people could assume it is a block. :) However had, having said that, I do not have a good alternative suggestion; and even then I think the feature may quite useful. Perhaps: struct a: 1, b: 2 Would be somewhat short too. Implying: struct {a: 1, b: 2} Though, admittedly the struct variant is still longer to type than the $ suggestion here. While I don't quite like the $, it is arguably short to type. And I suppose one requirement for this proposal is that it should be shorter than: Struct.new(:a, :b).new(1, 2) Even if made shorter, like: Struct.new(a: 1, b: 2) Admittedly the $ is shortest: ${a: 1, b: 2} matz' variant: foobar = {|a: 1, b: 2 |} foobar = {|a: 1, b: 2|} foobar = ${|a: 1, b: 2 |} (I was not sure which variant was the one; I assume matz' variant is actually without the $, so perhaps the second variant.) > If we name the anonymous class, the same member literals share the > name. s1 = ${a:1} s2 = ${a:2} Hmmmmm. The matz' variant would then be like this? s1 = {|a: 1|} To be honest, even if that is longer, I think it is better than the variant with $. I am not sure if I am the only one disliking the $ there but I think it would be better to not have to use $, even if the second variant is longer. But as said, I think the idea itself is fine. ---------------------------------------- Feature #16986: Anonymous Struct literal https://bugs.ruby-lang.org/issues/16986#change-86325 * Author: ko1 (Koichi Sasada) * Status: Open * Priority: Normal * Assignee: matz (Yukihiro Matsumoto) ---------------------------------------- # Abstract How about to introduce anonymous Struct literal such as `${a: 1, b: 2}`? It is almost same as `Struct.new(:a, :b).new(1, 2)`. # Proposal ## Background In many cases, people use hash objects to represents a set of values such as `person = {name: "ko1", country: 'Japan'}` and accesses it with `person[:name]` and so on. It is not easy to write (3 letters `[:]`!), and easy to introduce misspelling (`person[:nama]` doesn't raise an error). If we make a `Struct` objects such as `Person = Struct.new(:name, :age)` and `person = Person.new('ko1', 'Japan')`, we can access it with `person.name` naturally. However making new `Struct` is a cost of coding. Some cases we don't want to name (such as `Person`). Using `OpenStruct` (`person = OpenStruct.new(name: "ko1", country: "Japan")`), we can access it with `person.name`, but we can extend the fields and the performance is not good. Of course, we can define the class `Person` and attr_readers. But several lines we need. To summaries the issues: * Easy to Write * Don't need to declare the class * Accessible with `person.name` format * Limited fields * Better performance ## Idea Introduce new syntax to make an anonymous Struct literal such as: `${ a: 1, b: 2 }`. Similar to Hash syntax (with labels), but `$` prefix to recognize. Anonymous structs which has same member with same order share the 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 for this spec, we can specify the anonymous Struct class 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` similar 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 key names. ## Naming If we name the anonymous class, the same member literals share the name. ```ruby s1 = ${a:1} s2 = ${a:2} p [s1, s2] #=> [#, #] A = s1.class p [s1, s2] #=> [#, #] ``` Maybe it is not good behavior. -- https://bugs.ruby-lang.org/ Unsubscribe: