Issue #19000 has been updated by mame (Yusuke Endoh).
Discussed at the dev meeting.
Regarding method names, @matz likes `Data#update`. However, @ko1 objects because "update" is used as a destructive vocabulary in `Hash#update`. @matz said Data#with` is acceptable; Data#dup` is not acceptable.
----------------------------------------
Feature #19000: Data: Add "Copy with changes method" [Follow-on to #16122 Data: simple immutable value object]
https://bugs.ruby-lang.org/issues/19000#change-100422
* Author: RubyBugs (A Nonymous)
* Status: Open
* Priority: Normal
----------------------------------------
*As requested: extracted a follow-up to #16122 Data: simple immutable value object from [this comment](http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/109815)*
# Proposal: Add a "Copy with changes" method to Data
Assume the proposed `Data.define` exists.
Seeing examples from the [[Values gem]](https://github.com/ms-ati/Values):
```ruby
require "values"
# A new class
Point = Value.new(:x, :y)
# An immutable instance
Origin = Point.with(x: 0, y: 0)
# Q: How do we make copies that change 1 or more values?
right = Origin.with(x: 1.0)
up = Origin.with(y: 1.0)
up_and_right = right.with(y: up.y)
# In loops
movements = [
[ :x, +0.5 ],
[ :x, +0.5 ],
[ :y, -1.0 ],
[ :x, +0.5 ],
]
# position = Point(x: 1.5, y: -1.0)
position = movements.inject(Origin) do |p, (field, delta)|
p.with(field => p.send(field) + delta)
end
```
## Proposed detail: Call this method: `#with`
```ruby
Money = Data.define(:amount, :currency)
account = Money.new(amount: 100, currency: 'USD')
transactions = [+10, -5, +15]
account = transactions.inject(account) { |a, t| a.with(amount: a.amount + t) }
#=> Money(amount: 120, currency: "USD")
```
## Why add this "Copy with changes" method to the Data simple immutable value class?
Called on an instance, it returns a new instance with only the provided parameters changed.
This API affordance is now **widely adopted across many languages** for its usefulness. Why is it so useful? Because copying immutable value object instances, with 1 or more discrete changes to specific fields, is the proper and ubiquitous pattern that takes the place of mutation when working with immutable value objects.
**Other languages**
C# Records: “immutable record structs — Non-destructive mutation” — is called `with { ... }`
https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-t…
Scala Case Classes — is called `#copy`
https://docs.scala-lang.org/tour/case-classes.html
Java 14+ Records — Brian Goetz at Oracle is working on adding a with copy constructor inspired by C# above as we speak, likely to be called `#with`
https://mail.openjdk.org/pipermail/amber-spec-experts/2022-June/003461.html
Rust “Struct Update Syntax” via `..` syntax in constructor
https://doc.rust-lang.org/book/ch05-01-defining-structs.html#creating-insta…
## Alternatives
Without a copy-with-changes method, one must construct entirely new instances using the constructor. This can either be (a) fully spelled out as boilerplate code, or (b) use a symmetrical `#to_h` to feed the keyword-args constructor.
**(a) Boilerplate using constructor**
```ruby
Point = Data.define(:x, :y, :z)
Origin = Point.new(x: 0.0, y: 0.0, z: 0.0)
change = { z: -1.5 }
# Have to use full constructor -- does this even work?
point = Point.new(x: Origin.x, y: Origin.y, **change)
```
**(b) Using a separately proposed `#to_h` method and constructor symmetry**
```ruby
Point = Data.define(:x, :y, :z)
Origin = Point.new(x: 0.0, y: 0.0, z: 0.0)
change = { z: -1.5 }
# Have to use full constructor -- does this even work?
point = Point.new(**(Origin.to_h.merge(change)))
```
Notice that the above are not ergonomic -- leading so many of our peer language communities to adopt the `#with` method to copy an instance with discrete changes.
--
https://bugs.ruby-lang.org/
Issue #19000 has been updated by mame (Yusuke Endoh).
RubyBugs (A Nonymous) wrote in #note-21:
> How is this?
Thanks for the update.
Now I have a question. Do you really want to write `p.with(field => p.send(field) + delta)`? I don't think it is very elegant. It is not very convincing (at least, to me) as a first motivation example.
Also, do you need the ability to update multiple fields at once? Both motivation examples only update a single field. This may be the result of simplifying the motivation example, though.
Looking at these motivation examples, there may be room to consider an API like `p.with(field) {|old_value| old_value + delta }` or something.
----------------------------------------
Feature #19000: Data: Add "Copy with changes method" [Follow-on to #16122 Data: simple immutable value object]
https://bugs.ruby-lang.org/issues/19000#change-100421
* Author: RubyBugs (A Nonymous)
* Status: Open
* Priority: Normal
----------------------------------------
*As requested: extracted a follow-up to #16122 Data: simple immutable value object from [this comment](http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/109815)*
# Proposal: Add a "Copy with changes" method to Data
Assume the proposed `Data.define` exists.
Seeing examples from the [[Values gem]](https://github.com/ms-ati/Values):
```ruby
require "values"
# A new class
Point = Value.new(:x, :y)
# An immutable instance
Origin = Point.with(x: 0, y: 0)
# Q: How do we make copies that change 1 or more values?
right = Origin.with(x: 1.0)
up = Origin.with(y: 1.0)
up_and_right = right.with(y: up.y)
# In loops
movements = [
[ :x, +0.5 ],
[ :x, +0.5 ],
[ :y, -1.0 ],
[ :x, +0.5 ],
]
# position = Point(x: 1.5, y: -1.0)
position = movements.inject(Origin) do |p, (field, delta)|
p.with(field => p.send(field) + delta)
end
```
## Proposed detail: Call this method: `#with`
```ruby
Money = Data.define(:amount, :currency)
account = Money.new(amount: 100, currency: 'USD')
transactions = [+10, -5, +15]
account = transactions.inject(account) { |a, t| a.with(amount: a.amount + t) }
#=> Money(amount: 120, currency: "USD")
```
## Why add this "Copy with changes" method to the Data simple immutable value class?
Called on an instance, it returns a new instance with only the provided parameters changed.
This API affordance is now **widely adopted across many languages** for its usefulness. Why is it so useful? Because copying immutable value object instances, with 1 or more discrete changes to specific fields, is the proper and ubiquitous pattern that takes the place of mutation when working with immutable value objects.
**Other languages**
C# Records: “immutable record structs — Non-destructive mutation” — is called `with { ... }`
https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-t…
Scala Case Classes — is called `#copy`
https://docs.scala-lang.org/tour/case-classes.html
Java 14+ Records — Brian Goetz at Oracle is working on adding a with copy constructor inspired by C# above as we speak, likely to be called `#with`
https://mail.openjdk.org/pipermail/amber-spec-experts/2022-June/003461.html
Rust “Struct Update Syntax” via `..` syntax in constructor
https://doc.rust-lang.org/book/ch05-01-defining-structs.html#creating-insta…
## Alternatives
Without a copy-with-changes method, one must construct entirely new instances using the constructor. This can either be (a) fully spelled out as boilerplate code, or (b) use a symmetrical `#to_h` to feed the keyword-args constructor.
**(a) Boilerplate using constructor**
```ruby
Point = Data.define(:x, :y, :z)
Origin = Point.new(x: 0.0, y: 0.0, z: 0.0)
change = { z: -1.5 }
# Have to use full constructor -- does this even work?
point = Point.new(x: Origin.x, y: Origin.y, **change)
```
**(b) Using a separately proposed `#to_h` method and constructor symmetry**
```ruby
Point = Data.define(:x, :y, :z)
Origin = Point.new(x: 0.0, y: 0.0, z: 0.0)
change = { z: -1.5 }
# Have to use full constructor -- does this even work?
point = Point.new(**(Origin.to_h.merge(change)))
```
Notice that the above are not ergonomic -- leading so many of our peer language communities to adopt the `#with` method to copy an instance with discrete changes.
--
https://bugs.ruby-lang.org/
Issue #19117 has been updated by mame (Yusuke Endoh).
Discussed at the dev meeting. @matz was basically positive for this proposal. But there is still much to discuss and experiment, so it was decided to discuss for Ruby 3.3.
* How much impact does exception generation have on performance? (Each frame of the backtrace needs to maintain self)
* What notation to use (not discussed at this dev meeting due to priority on Ruby 3.2 issues)
----------------------------------------
Feature #19117: Include the method owner in backtraces, not just the method name
https://bugs.ruby-lang.org/issues/19117#change-100420
* Author: byroot (Jean Boussier)
* Status: Open
* Priority: Normal
----------------------------------------
```
module Foo
class Bar
def inspect
1 + '1'
end
end
end
p Foo::Bar.new
```
This code produce the following backtrace:
```
/tmp/foo.rb:4:in `+': String can't be coerced into Integer (TypeError)
from /tmp/foo.rb:4:in `inspect'
from /tmp/foo.rb:9:in `p'
from /tmp/foo.rb:9:in `<main>'
```
This works, but on large codebases and large backtraces the method name isn't always all that revealing, most of the time you need to open many of the locations listed in the backtrace to really understand what is going on.
I propose that we also include the owner name:
```
/tmp/foo.rb:4:in `Integer#+': String can't be coerced into Integer (TypeError)
from /tmp/foo.rb:4:in `Foo::Bar#inspect'
from /tmp/foo.rb:9:in `Kernel#p'
from /tmp/foo.rb:9:in `<main>'
```
I believe that in many case it would allow to much better understand the backtrace without having to jump back and forth between it and the source code.
This is inspired by @ivoanjo 's `backtracie` gem: https://github.com/ivoanjo/backtracie
--
https://bugs.ruby-lang.org/
Issue #19012 has been updated by mame (Yusuke Endoh).
This is what @akr said at the dev meeting. (My understanding)
> The proposed behavior might be possible for stream. On the other hand, for datagram, the current behavior is better. I am not sure if there is a portable way to determine if the file descriptor behind an IO object is stream or datagram.
----------------------------------------
Bug #19012: BasicSocket#recv* methods return an empty packet instead of nil on closed connections
https://bugs.ruby-lang.org/issues/19012#change-100419
* Author: byroot (Jean Boussier)
* Status: Open
* Priority: Normal
* Backport: 2.7: UNKNOWN, 3.0: UNKNOWN, 3.1: UNKNOWN
----------------------------------------
`man recvmsg(2)` states:
> Return Value
> These calls return the number of bytes received, or -1 if an error occurred. The return value will be 0 when the peer has performed an orderly shutdown.
But somehow the entire `receiv` family of methods in Ruby seem to interpret `0` as empty string instead of "EOF".
```ruby
require 'socket'
puts "=== pipes ==="
r, w = IO.pipe
r.read_nonblock(1, exception: false) # => :wait_readable
w.close
r.read_nonblock(1, exception: false) # => nil (EOF)
puts "=== sockets ===="
r, w = UNIXSocket.socketpair
r.read_nonblock(1, exception: false) # => :wait_readable
r.recvmsg_nonblock(1, exception: false) # => :wait_readable
r.recv_nonblock(1, exception: false) # => :wait_readable
w.close
r.read_nonblock(1, exception: false) # => nil (EOF)
r.recvmsg_nonblock(1, exception: false) # => ["", #<Addrinfo: empty-sockaddr SOCK_STREAM>, 128]]
r.recvmsg # => ["", #<Addrinfo: empty-sockaddr SOCK_STREAM>, 0]]
r.recv_nonblock(1, exception: false) # => ""
```
### Expected behavior
I would expect `recvmsg_nonblock`, `recvmsg`, `recv_nonblock` and `recv` to return `nil` when the connection is closed.
--
https://bugs.ruby-lang.org/
Issue #19147 has been updated by vo.x (Vit Ondruch).
Interestingly, testing with commit:git|0436f1e15a, there are now additional spec failures which appears to be the same issue:
~~~
1)
File.expand_path expands ~ENV['USER'] to the user's home directory ERROR
RuntimeError: can't set length of shared string
/builddir/build/BUILD/ruby-3.2.0-0436f1e15a/spec/ruby/core/file/expand_path_spec.rb:99:in `expand_path'
/builddir/build/BUILD/ruby-3.2.0-0436f1e15a/spec/ruby/core/file/expand_path_spec.rb:99:in `block (3 levels) in <top (required)>'
/builddir/build/BUILD/ruby-3.2.0-0436f1e15a/spec/ruby/core/file/expand_path_spec.rb:7:in `<top (required)>'
2)
File.expand_path expands ~ENV['USER']/a to a in the user's home directory ERROR
RuntimeError: can't set length of shared string
/builddir/build/BUILD/ruby-3.2.0-0436f1e15a/spec/ruby/core/file/expand_path_spec.rb:103:in `expand_path'
/builddir/build/BUILD/ruby-3.2.0-0436f1e15a/spec/ruby/core/file/expand_path_spec.rb:103:in `block (3 levels) in <top (required)>'
/builddir/build/BUILD/ruby-3.2.0-0436f1e15a/spec/ruby/core/file/expand_path_spec.rb:7:in `<top (required)>'
~~~
----------------------------------------
Bug #19147: `TestFileExhaustive#test_expand_path_for_existent_username` and `TestDir#test_home` fails on i686
https://bugs.ruby-lang.org/issues/19147#change-100412
* Author: vo.x (Vit Ondruch)
* Status: Open
* Priority: Normal
* ruby -v: ruby 3.2.0dev (2022-11-24 master 66e5200ba4) [i386-linux]
* Backport: 2.7: UNKNOWN, 3.0: UNKNOWN, 3.1: UNKNOWN
----------------------------------------
Testing with commit:git|66e5200ba4 on Fedora Rawhide, I observe following error just on i686 (other platforms are passing just fine):
~~~
1) Error:
TestFileExhaustive#test_expand_path_for_existent_username:
RuntimeError: can't set length of shared string
/builddir/build/BUILD/ruby-3.2.0-66e5200ba4/test/ruby/test_file_exhaustive.rb:1122:in `expand_path'
/builddir/build/BUILD/ruby-3.2.0-66e5200ba4/test/ruby/test_file_exhaustive.rb:1122:in `test_expand_path_for_existent_username'
2) Error:
TestDir#test_home:
RuntimeError: can't set length of shared string
/builddir/build/BUILD/ruby-3.2.0-66e5200ba4/test/ruby/test_dir.rb:537:in `expand_path'
/builddir/build/BUILD/ruby-3.2.0-66e5200ba4/test/ruby/test_dir.rb:537:in `block in test_home'
~~~
Previously testing with commit:git|4b1504ae0a, the tests were passing just fine.
--
https://bugs.ruby-lang.org/
Issue #19079 has been updated by jonathanhefner (Jonathan Hefner).
This issue occurred for a private module in Rails: https://github.com/rails/rails/pull/46189#discussion_r991440668.
Using `include` in a subclass works. Using `prepend` also works, and is the workaround I used for the Rails module.
However, my proposed solution for this issue (https://github.com/ruby/delegate/pull/14) also solves #19079 with a performance improvement.
I opened this issue and #19079 because the current behavior seemed surprising to me. In particular, I expected the `DelegateClass` block to behave just like a `Class.new` block. I feel like that is a reasonable assumption based on [the documentation](https://github.com/ruby/delegate/blob/2e1272cadbf86a02a0084d….
But, if my assumption is wrong, then I understand the decision.
----------------------------------------
Bug #19079: Modules included in a DelegateClass cannot override delegate methods
https://bugs.ruby-lang.org/issues/19079#change-100411
* Author: jonathanhefner (Jonathan Hefner)
* Status: Rejected
* Priority: Normal
* ruby -v: ruby 3.1.2p20
* Backport: 2.7: UNKNOWN, 3.0: UNKNOWN, 3.1: UNKNOWN
----------------------------------------
Because `DelegateClass` defines delegate methods on the class itself, those delegate methods come first in the method lookup chain. This prevents included modules from overriding delegate methods:
```ruby
Base = Class.new do
def foo
"base"
end
end
Helper = Module.new do
def foo
"helper"
end
end
WithHelper = DelegateClass(Base) { include Helper }
WithHelper.new(Base.new).foo
# => "base"
```
One possible solution would be to define the delegate methods in a separate module. That way, other modules could come before it in the method lookup chain.
--
https://bugs.ruby-lang.org/
Issue #19166 has been reported by alanwu (Alan Wu).
----------------------------------------
Bug #19166: Module#remove_method can change frozen modules when there is a prepended module
https://bugs.ruby-lang.org/issues/19166
* Author: alanwu (Alan Wu)
* Status: Open
* Priority: Normal
* ruby -v: 2.7, 3.0, 3.1, dev
* Backport: 2.7: UNKNOWN, 3.0: UNKNOWN, 3.1: UNKNOWN
----------------------------------------
```ruby
module A
prepend Module.new # remove this line and you'd get FrozenError as expected
def foo; end
freeze
remove_method :foo # remove works even though module is frozen!
p instance_methods(false) # => []
end
```
Old bug, reproduces in 2.7 through 3.1 and on master. Found while investigating #19164.
--
https://bugs.ruby-lang.org/
Issue #19143 has been updated by MSP-Greg (Greg L).
@nobu
Yes, I agree, it might be very helpful when one has more than one platform installed and uses `--user-install`. As in `Gem.install_extension_in_lib`, also [Gem::Ext::ExtConfBuilder](https://github.com/ruby/ruby/blob/master/lib/ruby…. I overwrite my Windows Ruby master installs daily, so I always use `--user-install`.
Maybe issues with existing extension gems that load with `require_relative`, and also pre-compiled gems?
----------------------------------------
Bug #19143: Windows - bundled extension gems compile, but don't copy *.so files to lib folder
https://bugs.ruby-lang.org/issues/19143#change-100408
* Author: MSP-Greg (Greg L)
* Status: Closed
* Priority: Normal
* Backport: 2.7: UNKNOWN, 3.0: UNKNOWN, 3.1: UNKNOWN
----------------------------------------
Just finished updating ruby-loco's mswin build to use a system similar to the ucrt & mingw builds.
Confirmed something I noticed previously, and also occurs with the RubyInstaller2 head build.
On Windows, bundled extension gems (debug, rbs) compile their extension in the `ext` folder, but do not copy them to the `lib` folder. So, the *.so file is created, but not copied.
I think this was working correctly on Ruby 3.1?
--
https://bugs.ruby-lang.org/
Issue #19079 has been updated by Hanmac (Hans Mackowiak).
you can also try prepend instead of include:
```ruby
WithHelper = DelegateClass(Base) { prepend Helper }
```
----------------------------------------
Bug #19079: Modules included in a DelegateClass cannot override delegate methods
https://bugs.ruby-lang.org/issues/19079#change-100407
* Author: jonathanhefner (Jonathan Hefner)
* Status: Rejected
* Priority: Normal
* ruby -v: ruby 3.1.2p20
* Backport: 2.7: UNKNOWN, 3.0: UNKNOWN, 3.1: UNKNOWN
----------------------------------------
Because `DelegateClass` defines delegate methods on the class itself, those delegate methods come first in the method lookup chain. This prevents included modules from overriding delegate methods:
```ruby
Base = Class.new do
def foo
"base"
end
end
Helper = Module.new do
def foo
"helper"
end
end
WithHelper = DelegateClass(Base) { include Helper }
WithHelper.new(Base.new).foo
# => "base"
```
One possible solution would be to define the delegate methods in a separate module. That way, other modules could come before it in the method lookup chain.
--
https://bugs.ruby-lang.org/