[ruby-core:113033] [Ruby master Feature#19555] Allow passing default options to `Data.define`

Issue #19555 has been reported by p8 (Petrik de Heus). ---------------------------------------- Feature #19555: Allow passing default options to `Data.define` https://bugs.ruby-lang.org/issues/19555 * Author: p8 (Petrik de Heus) * Status: Open * Priority: Normal ---------------------------------------- Defining a subclass of `Data` with default attributes can currently be implemented by overriding `intialize`: ```ruby class Point < Data.define(:x, :y, :z) def initialize(x:, y:, z: 0) = super end p Point.new(1, 2) #=> #<data Point x=1, y=2, z=0> ``` It would be nice if we could do it in `define` as well: ```ruby Point = Data.define(:x, :y, z: 0) ``` -- https://bugs.ruby-lang.org/

Issue #19555 has been updated by zverok (Victor Shepelev). This is not simple as it seems (that's why we abstained of introducing something like that). Imagine this: ```ruby Point = Data.define(:x, :y, tags: []) p1 = Point.new(1, 2) p1.tags << 'foo' p2 = Point2.new(3, 4) p2.tags #=>? ``` In naive implementation, `p1.tags` is the same ary as passed for default, and `p1.tags << 'foo'` adjusts the default value. In less naive implementation, we need probably `.dup` defaults, which still might be not enough or impossible. Yes, mutable values are kinda against Data idea, but it doesn’t mean nobody will do that. ---------------------------------------- Feature #19555: Allow passing default options to `Data.define` https://bugs.ruby-lang.org/issues/19555#change-102574 * Author: p8 (Petrik de Heus) * Status: Open * Priority: Normal ---------------------------------------- Defining a subclass of `Data` with default attributes can currently be done by overriding `intialize`: ```ruby class Point < Data.define(:x, :y, :z) def initialize(x:, y:, z: 0) = super end p Point.new(1, 2) #=> #<data Point x=1, y=2, z=0> ``` It would be nice if we could do it in `define` as well: ```ruby Point = Data.define(:x, :y, z: 0) ``` -- https://bugs.ruby-lang.org/

Issue #19555 has been updated by p8 (Petrik de Heus). Thanks for the explanation @zverok. That makes sense. ---------------------------------------- Feature #19555: Allow passing default options to `Data.define` https://bugs.ruby-lang.org/issues/19555#change-102581 * Author: p8 (Petrik de Heus) * Status: Open * Priority: Normal ---------------------------------------- Defining a subclass of `Data` with default attributes can currently be done by overriding `intialize`: ```ruby class Point < Data.define(:x, :y, :z) def initialize(x:, y:, z: 0) = super end p Point.new(1, 2) #=> #<data Point x=1, y=2, z=0> ``` It would be nice if we could do it in `define` as well: ```ruby Point = Data.define(:x, :y, z: 0) ``` -- https://bugs.ruby-lang.org/

Issue #19555 has been updated by FlickGradley (Nick Bradley). I'm curious about this as well. Would it make sense to have a separate method `.with_defaults`, that checks the mutability (i.e. `.frozen?`) of its arguments? If the intention is for Data to be "deeply" immutable, I could see this making sense. Then again, I'm not sure if there is a way to detect mutability of all objects "belonging to" a given parent object. I guess if there were such a way, it would have been included in Data already, as a way to enforce its immutability. Perhaps this sort of thing should exist in ActiveSupport instead, or as a gem, since its immutability checking cannot be guaranteed to work in all cases (but could be written to work for most). This is effectively the same problem as Python has with mutable default arguments. ---------------------------------------- Feature #19555: Allow passing default options to `Data.define` https://bugs.ruby-lang.org/issues/19555#change-102606 * Author: p8 (Petrik de Heus) * Status: Open * Priority: Normal ---------------------------------------- Defining a subclass of `Data` with default attributes can currently be done by overriding `intialize`: ```ruby class Point < Data.define(:x, :y, :z) def initialize(x:, y:, z: 0) = super end p Point.new(1, 2) #=> #<data Point x=1, y=2, z=0> ``` It would be nice if we could do it in `define` as well: ```ruby Point = Data.define(:x, :y, z: 0) ``` -- https://bugs.ruby-lang.org/

Issue #19555 has been updated by FlickGradley (Nick Bradley). To clarify what I mean, this is a (very rough) demonstration: ``` ruby class Data def self.with_defaults(*symbols, **defaults, &block) defaults&.each { |key, value| raise ArgumentError, "#{key} must be immutable" unless Ractor.shareable?(value) } Data.define(*(symbols + defaults.keys)) do @@defaults = defaults class_eval(&block) if block def initialize(**kwargs) @@defaults.each do |key, value| kwargs[key] = value unless kwargs.key?(key) end super(**kwargs) end end end end Point = Data.with_defaults(:x, :y, z: [].freeze) do def +(other) = self.class.new(self.x + other.x, self.y + other.y) end p1 = Point.new(x: 1, y: 2) p2 = Point.new(x: 3, y: 4) p3 = p1 + p2 ``` This isn't meant to be an actual proposal (lots of holes in this) - but, conceptually, I'm curious if the Ractor's concept of `shareable?` could or should be applied to Data, since it _is_ supposed to be immutable. ---------------------------------------- Feature #19555: Allow passing default options to `Data.define` https://bugs.ruby-lang.org/issues/19555#change-102614 * Author: p8 (Petrik de Heus) * Status: Open * Priority: Normal ---------------------------------------- Defining a subclass of `Data` with default attributes can currently be done by overriding `intialize`: ```ruby class Point < Data.define(:x, :y, :z) def initialize(x:, y:, z: 0) = super end p Point.new(1, 2) #=> #<data Point x=1, y=2, z=0> ``` It would be nice if we could do it in `define` as well: ```ruby Point = Data.define(:x, :y, z: 0) ``` -- https://bugs.ruby-lang.org/

Issue #19555 has been updated by ozydingo (Andrew Schwartz). I've found the need for something like this as well, I have a use case where it's getting repetitive and distractingly verbose to build out the `Data` blocks just for a number of defaults. The mutable defaults is a really good point, though it's not too dissimilar from existing behavior in Ruby's Hash with defaults -- ```rb h = Hash.new([]) h[1] << 'foo' h[2] #=> ['foo'] ``` So in a sense there is prior art here and a pattern that Rubyists already have to be aware of. ---------------------------------------- Feature #19555: Allow passing default options to `Data.define` https://bugs.ruby-lang.org/issues/19555#change-111728 * Author: p8 (Petrik de Heus) * Status: Open ---------------------------------------- Defining a subclass of `Data` with default attributes can currently be done by overriding `intialize`: ```ruby class Point < Data.define(:x, :y, :z) def initialize(x:, y:, z: 0) = super end p Point.new(1, 2) #=> #<data Point x=1, y=2, z=0> ``` It would be nice if we could do it in `define` as well: ```ruby Point = Data.define(:x, :y, z: 0) ``` -- https://bugs.ruby-lang.org/

Issue #19555 has been updated by matz (Yukihiro Matsumoto). Status changed from Open to Closed As a programmer, I can imagine the convenience of this default value for Data attributes, but as @zverok mentioned, I can also imagine the trouble caused by this (just like Python's famous optional argument issue). So as a conclusion, we do not adopt this proposal. Matz. ---------------------------------------- Feature #19555: Allow passing default options to `Data.define` https://bugs.ruby-lang.org/issues/19555#change-111869 * Author: p8 (Petrik de Heus) * Status: Closed ---------------------------------------- Defining a subclass of `Data` with default attributes can currently be done by overriding `intialize`: ```ruby class Point < Data.define(:x, :y, :z) def initialize(x:, y:, z: 0) = super end p Point.new(1, 2) #=> #<data Point x=1, y=2, z=0> ``` It would be nice if we could do it in `define` as well: ```ruby Point = Data.define(:x, :y, z: 0) ``` -- https://bugs.ruby-lang.org/
participants (5)
-
FlickGradley (Nick Bradley)
-
matz (Yukihiro Matsumoto)
-
ozydingo (Andrew Schwartz)
-
p8 (Petrik de Heus)
-
zverok (Victor Shepelev)