From: josef.simanek@gmail.com
To: ruby-core@ruby-lang.org
Subject: [ruby-core:98987] [Ruby master Feature#16986] Anonymous Struct literal
Date: Sat, 27 Jun 2020 18:31:08 +0000 (UTC) [thread overview]
Message-ID: <redmine.journal-86367.20200627183108.17@ruby-lang.org> (raw)
In-Reply-To: <redmine.issue-16986.20200626065853.17@ruby-lang.org>
Issue #16986 has been updated by retro (Josef Šimánek).
First of all, this is super cool idea!
I do have habit to use hash since it is seems to be elegant (as described in original proposal background section) and I end up having problems later (since I need to use fetch everywhere to get at least some kind of consistency and to avoid typos for example).
I think there's no need for new syntax. "Struct.new" and "Kernel.Struct()" should be enough (if possible to extend Struct.new and keep the same performance).
Regarding syntax used, it would be great to support also "nested" structs, which I'm not sure if possible for all current ideas. For example:
``` ruby
config = Struct(assets: Struct(reload: true))
config.assets.reload #=> true
```
For simple structs I think %t or %o notation would be handy as well.
As mentioned at [#10](https://bugs.ruby-lang.org/issues/16986#note-10), by introducing Struct(), extending Struct.new and allowing %o or %t it would just follow already common patterns used for Array and Hash.
----------------------------------------
Feature #16986: Anonymous Struct literal
https://bugs.ruby-lang.org/issues/16986#change-86367
* 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] #=> [#<struct a=1>, #<struct a=2>]
A = s1.class
p [s1, s2] #=> [#<struct A a=1>, #<struct A a=2>]
```
Maybe it is not good behavior.
--
https://bugs.ruby-lang.org/
next prev parent reply other threads:[~2020-06-27 18:31 UTC|newest]
Thread overview: 66+ messages / expand[flat|nested] mbox.gz Atom feed top
2020-06-26 6:58 [ruby-core:98947] " ko1
2020-06-26 9:36 ` [ruby-core:98949] " shevegen
2020-06-26 10:09 ` [ruby-core:98951] " manga.osyo
2020-06-26 11:05 ` [ruby-core:98952] " zverok.offline
2020-06-26 11:08 ` [ruby-core:98953] " zverok.offline
2020-06-26 11:14 ` [ruby-core:98954] " jean.boussier
2020-06-26 11:15 ` [ruby-core:98955] " jean.boussier
2020-06-26 16:22 ` [ruby-core:98961] " zverok.offline
2020-06-27 12:37 ` [ruby-core:98981] " info+ruby
2020-06-27 14:28 ` [ruby-core:98982] " brooke
2020-06-27 17:55 ` [ruby-core:98985] " ko1
2020-06-27 18:08 ` [ruby-core:98986] " ko1
2020-06-27 18:31 ` josef.simanek [this message]
2020-07-02 8:44 ` [ruby-core:99027] " hanmac
2020-07-02 11:34 ` [ruby-core:99028] " gabriel.sobrinho
2020-07-02 16:06 ` [ruby-core:99030] " caleb
2020-07-02 17:02 ` [ruby-core:99035] " jean.boussier
2020-07-02 18:58 ` [ruby-core:99037] " ko1
2020-07-02 20:56 ` [ruby-core:99039] " marcandre-ruby-core
2020-07-02 23:14 ` [ruby-core:99041] " dimasamodurov
2020-07-03 10:52 ` [ruby-core:99043] " esquinas.enrique
2020-07-03 13:31 ` [ruby-core:99045] " hanmac
2020-07-07 15:07 ` [ruby-core:99076] " jonathan
2020-07-10 8:16 ` [ruby-core:99109] " lgemon
2020-08-04 0:40 ` [ruby-core:99470] " ko1
2020-08-12 17:56 ` [ruby-core:99571] " masafumi.o1988
2020-08-12 19:48 ` [ruby-core:99573] " manga.osyo
2020-08-13 5:56 ` [ruby-core:99575] " nobu
2020-08-14 5:17 ` [ruby-core:99587] " masafumi.o1988
2020-08-17 6:09 ` [ruby-core:99606] " nobu
2020-08-17 7:16 ` [ruby-core:99607] " sawadatsuyoshi
2020-08-18 17:05 ` [ruby-core:99629] " esquinas.enrique
2020-08-19 19:05 ` [ruby-core:99648] " ko1
2020-08-20 14:48 ` [ruby-core:99660] " esquinas.enrique
2020-08-21 8:51 ` [ruby-core:99661] " mame
2020-08-24 3:13 ` [ruby-core:99676] " daniel
2020-08-24 13:43 ` [ruby-core:99682] " mame
2020-08-27 0:13 ` [ruby-core:99717] " ko1
2020-08-30 10:46 ` [ruby-core:99777] " nobu
2020-09-02 12:17 ` [ruby-core:99836] " duerst
2020-09-02 17:21 ` [ruby-core:99847] " marcandre-ruby-core
2020-09-03 1:32 ` [ruby-core:99860] " duerst
2020-09-28 19:37 ` [ruby-core:100203] " chris
2020-09-28 20:57 ` [ruby-core:100204] " marcandre-ruby-core
2020-09-29 14:35 ` [ruby-core:100215] " esquinas.enrique
2020-09-29 15:57 ` [ruby-core:100218] " marcandre-ruby-core
2020-09-29 16:15 ` [ruby-core:100219] " tenderlove
2020-09-29 17:15 ` [ruby-core:100222] " marcandre-ruby-core
2020-09-29 20:27 ` [ruby-core:100225] " tenderlove
2020-09-30 1:36 ` [ruby-core:100226] " mame
2020-09-30 1:47 ` [ruby-core:100227] " marcandre-ruby-core
2020-09-30 13:08 ` [ruby-core:100235] " eregontp
2020-10-15 15:27 ` [ruby-core:100405] " ko1
2020-10-15 16:40 ` [ruby-core:100406] " marcandre-ruby-core
2020-10-15 17:40 ` [ruby-core:100407] " ko1
2020-10-15 17:43 ` [ruby-core:100408] " ko1
2020-10-15 19:10 ` [ruby-core:100409] " eregontp
2020-12-14 20:19 ` [ruby-core:101451] " shannonskipper
2022-01-15 16:57 ` [ruby-core:107141] " briankung (Brian Kung)
2022-12-31 1:43 ` [ruby-core:111548] " Bumppoman (Brendon Stanton) via ruby-core
2022-12-31 2:04 ` [ruby-core:111549] " matz (Yukihiro Matsumoto) via ruby-core
2022-12-31 17:52 ` [ruby-core:111555] " Eustáquio Rangel via ruby-core
2022-12-31 7:04 ` [ruby-core:111550] " shyouhei (Shyouhei Urabe) via ruby-core
2024-10-15 18:54 ` [ruby-core:119531] " danh337 (Dan H) via ruby-core
2024-10-15 19:44 ` [ruby-core:119532] " Eregon (Benoit Daloze) via ruby-core
2024-10-21 8:50 ` [ruby-core:119551] " danh337 (Dan H) via ruby-core
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=redmine.journal-86367.20200627183108.17@ruby-lang.org \
--to=josef.simanek@gmail.com \
--cc=ruby-core@ruby-lang.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).