[#99868] [Ruby master Bug#17144] Tempfile.open { ... } does not unlink the file — eregontp@...
Issue #17144 has been reported by Eregon (Benoit Daloze).
15 messages
2020/09/03
[ruby-core:100222] [Ruby master Feature#16986] Anonymous Struct literal
From:
marcandre-ruby-core@...
Date:
2020-09-29 17:15:37 UTC
List:
ruby-core #100222
Issue #16986 has been updated by marcandre (Marc-Andre Lafortune).
tenderlovemaking (Aaron Patterson) wrote in #note-48:
> I frequently use `Struct.new().new()` in test cases where I need fakes.
> Here is some real code that I think `%struct{...}` would improve:
>
> https://github.com/rails/rails/blob/6fca0f31f14db4e8b73a6c1d89afd16d51e6b6f3/activerecord/test/cases/reflection_test.rb#L422-L423
> https://github.com/rails/rails/blob/6fca0f31f14db4e8b73a6c1d89afd16d51e6b6f3/activerecord/test/cases/reflection_test.rb#L437-L438
> https://github.com/rails/rails/blob/6fca0f31f14db4e8b73a6c1d89afd16d51e6b6f3/activerecord/test/cases/reflection_test.rb#L452-L453
Thanks for your reply and looking into the Rails codebase.
I actually think these examples might actually benefit from a helper method to create a fake table schema. It would be clearer and if ever the minimal implementation of an activerecord schema was change, say to require an extra method, you'd have to change all these different calls instead of one.
That being said, I understand that creating test fakes is a circumstance where one might want to do `Struct.new().new()`, maybe the only one actually. I'm not convinced that this warrants a dedicated syntax, but maybe that's because I try my best to avoid fakes in tests.
> Here is a non-test case:
>
> https://github.com/rails/rails/blob/6fca0f31f14db4e8b73a6c1d89afd16d51e6b6f3/actionview/lib/action_view/template/types.rb#L9
>
> Actually it's pretty trivial to find these places in the Rails codebase. `git grep 'Struct.new([^)]*)\.new'` will give you quite a few examples of real world code that could be improved with this syntax.
Actually I couldn't find any other non-test example in Rails than the one you provided above. I don't know how many LOC are in the `lib` of Rails, but if in the whole implementation there is only a single use for it, I think that this only validates that there is *no need for a special syntax*.
Let us remember there is a cost for a special syntax. Tooling needs to be updated (`parser`, `rubocop`, ...) and more importantly, this increases the cognitive load. In this case, the gain does not justify it.
I have a different proposal instead, which is defining `Struct.[]`:
```ruby
Struct[name: 'Joe', id: 42] # => Struct.new(:name, :id, keyword_init: true).new(name: 'Joe', id: 42)
```
No new syntax required. Less of cognitive load as it is a simple shortcut. A note in the doc that this won't be particularly performant and best reserved for test fakes or static constants.
----------------------------------------
Feature #16986: Anonymous Struct literal
https://bugs.ruby-lang.org/issues/16986#change-87811
* 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] #=> [#<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/
Unsubscribe: <mailto:ruby-core-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-core>