Feature #16986

Updated by sawa (Tsuyoshi Sawada) 3 months ago

# Abstract 

 How about introducing to introduce anonymous Struct literal such as `${a: 1, b: 2}`? 
 It is almost the same as `, :b).new(1, 2)`. 

 # Proposal 

 ## Background 

 In many cases, people use hash objects to represent represents a set of values such as `person = {name: "ko1", country: 'Japan'}` and access its values through accesses it with `person[:name]` and so on. It is not easy to write (three characters (3 letters `[:]`!), and it easily introduces easy to introduce misspelling (`person[:nama]` doesn't raise an error). 

 If we make a `Struct` object by doing objects such as `Person =, :age)` and `person ='ko1', 'Japan')`, we can access its values through it with `` naturally. However, it costs 
 However making new `Struct` is a cost of coding. And in some cases, Some cases we don't    want to name the class (such as `Person`). 

 Using `OpenStruct` (`person = "ko1", country: "Japan")`), we can access it through with ``, but we can extend the fields unintentionally, and the performance is not good. 

 Of course, we can define a the class `Person` with and attr_readers. But it takes several lines. lines we need. 

 To summarize summaries the needs: issues: 

 * Easy to write Write 
   * Doesn't require declaring Don't need to declare the class 
   * Accessible through with `` format 
 * Limited fields 
 * Better performance 

 ## Idea 

 Introduce new literal syntax for to make an anonymous Struct literal such as: `${ a: 1, b: 2 }`. 
 Similar to Hash syntax (with labels), but with `$` prefix to distinguish. recognize. 

 Anonymous structs which have the has same member in the with same order share their the class. 

     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 for this spec, we can specify the anonymous Struct classes class at compile time. 
 We don't need to find or create Struct classes at runtime. 

 ## Implementatation 

 # Discussion 

 ## Notation 

 Matz said he thought about `{|a: 1, b: 2 |}` syntax. 

 ## Performance 

 Surprisingly, Hash is fast and Struct is slow. 

 Benchmark.driver do |r| 
   r.prelude <<~PRELUDE 
   st =, :b).new(1, 2) 
   hs = {a: 1, b: 2} 
   class C 
     attr_reader :a, :b 
     def initialize() = (@a = 1; @b = 2) 
   ob = 
   PRELUDE "ob.a" "hs[:a]" "st.a" 
 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) 
                 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 similar to ivar accesses, so we can improve the performance. 

 BTW, OpenStruct (os.a) is slow. 

               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 the anonymous class, literals with the same members member literals share the name. 

 s1 = ${a:1} 
 s2 = ${a:2} 
 p [s1, s2] #=> [#<struct a=1>, #<struct a=2>] 
 A = s1.class 
 p [s1, s2] #=> [#<struct A a=1>, #<struct A a=2>] 


 Maybe that it is not a good behavior.