1. 6
    1. 4
      Point = Data.define(:x, :y)
      
      p1 = Point.new(1, 2)       #=> #<data Point x=1, y=2>
      

      … what would you expect to be Data#initialize signature?

      I was confused why Data#initialize was relevant to the question instead of Point#initialize. It would be helpful to give the reader the necessary background knowledge that Data.define returns a class that inherits from Data and that doesn’t have an overridden #initialize instance method:

      p Point.ancestors
      # [Point, Data, Object, Kernel, BasicObject]
      
      p Point.instance_method(:initialize)
      # #<UnboundMethod: Data#initialize(*)>
      
      p Point.instance_method(:initialize) == Data.instance_method(:initialize)
      # true
      

      That’s why Data#initialize is relevant.

      … the [Data#initialize] signature is actually this:

      def initialize(x:, y:)
      end
      

      That can’t be correct. That could only be true if Point = Data.define(:x, :y) redefined Data#initialize. That new definition of Data#initialize would break initialization of any other Data subclasses, such as MyComplex = Data.define(:real, :imaginary).

      I wondered if you had meant to ask about the signature of Point#initialize instead of Data#initialize. However, that can’t be what you meant either because the output above shows that Point.instance_method(:initialize) == Data.instance_method(:initialize).

      As far as irb can tell, Point#initialize and Data#initialize take variable parameters:

      >> pi = Point.instance_method(:initialize)
      => #<UnboundMethod: Data#initialize(*)>
      >> pi.source_location # “returns … nil if this method was not defined in Ruby (i.e. native)”
      => nil
      >> pi.arity # “for methods written in C, returns -1 if the call takes a variable number of arguments”
      => -1
      >> pi.parameters
      => [[:rest]]
      

      You can check it by redefining initialize

      Point = Data.define(:x, :y) do
        def initialize(...)
          # …
        end
      end
      

      The docs for Data say “::define method accepts an optional block and evaluates it in the context of the newly defined class.” Thus, the above code redefines Point#initialize, not Data#initialize.

      1. 3

        I was confused why Data#initialize was relevant to the question instead of Point#initialize

        Yeah, writing here Point#initialize would be clearer, thanks. It was just a habitual slip (because I knew that Point#initialize, if not redefined, would be Data#initialize

        the [Data#initialize] signature is actually this: …

        That can’t be correct.

        Yes, again, what I meant here is Point#initialize, and saying it is “actually this” is wrong, it is rather “effectively this”, e.g. behaves like this. Though, as a method defined in C, its declared signature is “give me everything, I’ll check it internally”.

        Thank you for valuable remarks, I’ll adjust the post!

      2. 2

        This is somewhat unexpected (and was already several times reported as a bug in the official tracker

        The assumption that this is unexpected fascinates me.

        My work at #{dayjob} involves regular ruby mentorship and I find that neither very new nor very experienced developers would find this unexpected. There is, however, very much a middle group who assume everything in ruby-like languages is a bodge of arrays and dicts under the hood.

        I’m always curious about the relative size of these developer populations as it affects how we do code reviews and explanations. I wonder where this comes from.

        1. 1

          This is somewhat unexpected (and was already several times reported as a bug in the official tracker!)

          then… it’s a bug. Once or twice? I get it… But several times is definitely a bug.