From: jonathan@... Date: 2020-07-07T15:07:24+00:00 Subject: [ruby-core:99076] [Ruby master Feature#16986] Anonymous Struct literal Issue #16986 has been updated by jrochkind (jonathan rochkind). Why is more special syntax needed, when it can just be a method? ``` def Kernel.AStruct(**key_values) Struct.new(key_values.keys).new(key_values.values) end AStruct(a: 1, b: 2) ``` If that implementation isn't efficient enough, implement it in C with a high-performance memoizing implementation or something, why not. But additional syntax makes the language larger, harder to implement, harder to learn. It's hard to google for punctuation. At its best Ruby is just objects and methods, if we can provide this functionality just fine with a plain old method, why the need for new syntax? ---------------------------------------- Feature #16986: Anonymous Struct literal https://bugs.ruby-lang.org/issues/16986#change-86450 * 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/ Unsubscribe: