ruby-core@ruby-lang.org archive (unofficial mirror)
 help / color / mirror / Atom feed
From: "danh337 (Dan H) via ruby-core" <ruby-core@ml.ruby-lang.org>
To: ruby-core@ml.ruby-lang.org
Cc: "danh337 (Dan H)" <noreply@ruby-lang.org>
Subject: [ruby-core:119551] [Ruby master Feature#16986] Anonymous Struct literal
Date: Mon, 21 Oct 2024 08:50:18 +0000 (UTC)	[thread overview]
Message-ID: <redmine.journal-110165.20241018200604.17@ruby-lang.org> (raw)
In-Reply-To: <redmine.issue-16986.20200626065853.17@ruby-lang.org>

Issue #16986 has been updated by danh337 (Dan H).


Eregon (Benoit Daloze) wrote in #note-69:
> That's pretty slow and inefficient as it creates a new Struct subclass for every Hash it's applied to.
> The core idea of this proposal is to make it syntax and disallow **h to make sure that doesn't happen.

Um ok so I'm voting against a new syntax. It's not necessary. A simple method like the one above is pretty good evidence. Whatever javascript is doing is not evidence for Ruby.

When you say "slow and inefficient" you must be comparing to something else but it isn't clear what you're thinking. If you're comparing to a new notation which does not handle recursive nesting of Struct objects, then of course you're right. But then that notation would be even easier to obviate with a simple method.

And then if a new syntax did handle nested Struct objects, then, um there are new Struct objects? Shrug.

> This example could be a decent use case for OpenStruct, but that would need to be fixed perf-wise first, which is hard because of compatibility concerns: https://github.com/ruby/ostruct/issues/51
> Wrapping it in your own OpenStruct-like wrapper would likely be the best for now.

Sorry I don't understand "best for now". This example is a way to AVOID OpenStruct. The nice things about Struct are (a) it catches typos, and (b) it's way faster than OpenStruct.

I don't want smart Ruby folks like @Eregon to spend a lot of time on this. I just wanted to show that a new syntax is not really needed.


----------------------------------------
Feature #16986: Anonymous Struct literal
https://bugs.ruby-lang.org/issues/16986#change-110165

* Author: ko1 (Koichi Sasada)
* Status: Assigned
* 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] #=> [#<struct a=1>, #<struct a=2>]
A = s1.class
p [s1, s2] #=> [#<struct A a=1>, #<struct A a=2>]

```

Maybe that is not a good behavior.




-- 
https://bugs.ruby-lang.org/
 ______________________________________________
 ruby-core mailing list -- ruby-core@ml.ruby-lang.org
 To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
 ruby-core info -- https://ml.ruby-lang.org/mailman3/lists/ruby-core.ml.ruby-lang.org/

      parent reply	other threads:[~2024-10-21  9:05 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 ` [ruby-core:98987] " josef.simanek
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 ` danh337 (Dan H) via ruby-core [this message]

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-110165.20241018200604.17@ruby-lang.org \
    --to=ruby-core@ml.ruby-lang.org \
    --cc=noreply@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).