Project

General

Profile

Actions

Feature #19555

open

Allow passing default options to `Data.define`

Added by p8 (Petrik de Heus) about 1 year ago. Updated about 1 year ago.

Status:
Open
Assignee:
-
Target version:
-
[ruby-core:113033]

Description

Defining a subclass of Data with default attributes can currently be done by overriding intialize:

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:

Point = Data.define(:x, :y, z: 0)
Actions #1

Updated by p8 (Petrik de Heus) about 1 year ago

  • Description updated (diff)

Updated by zverok (Victor Shepelev) about 1 year ago

This is not simple as it seems (that's why we abstained of introducing something like that).

Imagine this:

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.

Updated by p8 (Petrik de Heus) about 1 year ago

Thanks for the explanation @zverok (Victor Shepelev).
That makes sense.

Updated by FlickGradley (Nick Bradley) about 1 year ago

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.

Updated by FlickGradley (Nick Bradley) about 1 year ago

To clarify what I mean, this is a (very rough) demonstration:

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.

Actions

Also available in: Atom PDF

Like1
Like0Like0Like0Like0Like0