Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introspection into Object::Pad-based objects #164

Open
leonerd opened this issue Jul 1, 2021 · 5 comments
Open

Introspection into Object::Pad-based objects #164

leonerd opened this issue Jul 1, 2021 · 5 comments

Comments

@leonerd
Copy link
Contributor

leonerd commented Jul 1, 2021

You may or maynot be aware of Object::Pad - in brief it's an experimental syntax module in preparation of a true in-core object system. As part of the periphery around this we are wondering how it would interact with things like Data::Printer.

Would youfolks be open to some suggestions on how to operate this?

@garu
Copy link
Owner

garu commented Jul 1, 2021

Absolutely! Just let us know 🙃

@leonerd
Copy link
Contributor Author

leonerd commented Jul 1, 2021

This particular piece of code was a proof-of-concept from a unit test:

   use v5.26;
   use feature 'signatures';
   use List::Util 'max';

   sub dump_object ( $obj )
   {
      my $metaclass = Object::Pad::MOP::Class->for_class( ref $obj );

      my @metas;
      while( $metaclass ) {
         # TODO: roles
         push @metas, $metaclass;

         ( $metaclass ) = $metaclass->superclasses;
      }

      my @output;

      foreach my $metaclass ( @metas ) {
         foreach my $metaslot ( $metaclass->slots ) {
            push @output, [
               join( "/", $metaclass->name, $metaslot->name ),
               $metaslot->value( $obj )
            ];
         }
      }

      # Technically a Unicode bug in here but printf is dumb

      my $maxwidth = max map { length $_->[0] } @output;

      return join "\n", map {
         sprintf "%-*s | %s", $maxwidth, $_->[0], $_->[1] // "<undef>";
      } @output;
   }

Gives output:

Tickit::Widget::Button/$_label            | Click me
Tickit::Widget::Button/$_on_click         | CODE(0x55e9d43bb880)
Tickit::Widget::Button/$_active           | 0
Tickit::Widget::Button/$_dragging_on_self | <undef>
Tickit::Widget::Button/$_label_line       | 1
Tickit::Widget::Button/$_label_col        | 3
Tickit::Widget::Button/$_label_end        | 11

Obviously here all I've done is stringified the values of the slots, but you can see all the pieces are there to let any sort of recursive dumper module walk the slot values in the same way as it might e.g. walk the values of a hash and recurse into those values on the way down.

It doesn't yet handle slots included from roles, but that's an open Object::Pad API design question.

@garu
Copy link
Owner

garu commented Jul 8, 2021

Hey @leonerd! Quick questions:

  • is the order returned from $meta->slots guaranteed to be the same? Is it the same as the order in which the declarations were made on the class? Moo(se) has an insertion_order() property on a class' attributes, I was wondering if it makes sense to expect the same here.

  • when introspecting a slot, I can get the current value with $slot->value( $instance ). How would I get the default value?

  • has_param and param_name are quite interesting. However, I would also be interested in knowing whether a param is required or not for the constructor.

My proposal is that Object::Pad::MOP::Slot gains the following new methods:

has_default => true if a default value has been assigned to the slot

$slot->default( $instance ) => the default value assigned to the instance. Like value() it could also be an lvalue mutator for scalar slots.

Another approach is to get rid of has_param and param_name altogether in favor of has_attribute, attribute_name and attribute_value, which could be used not just to "param" but also "reader", "writer" and "mutator" (which would likely come next in my search for metadata to print). And in that case you would have to promote default to an attribute as well, even if one that can also be set via parameters.

What do you think? Does any of that make any sense to you?

Cheers!

@leonerd
Copy link
Contributor Author

leonerd commented Jul 13, 2021

* is the order returned from `$meta->slots` guaranteed to be the same? Is it the same as the order in which the declarations were made on the class? Moo(se) has an `insertion_order()` property on a class' attributes, I was wondering if it makes sense to expect the same here.

Yes - it should come in declaration order, for each (partial) class.

* when introspecting a slot, I can get the current value with `$slot->value( $instance )`. How would I get the _default_ value?

Ooh, fun question. Currently you can't - you can't even know that a default exists. Would you need to know such, for Data::Printer?

* `has_param` and `param_name` are quite interesting. However, I would also be interested in knowing whether a param is required or not for the constructor.

My proposal is that Object::Pad::MOP::Slot gains the following new methods:

has_default => true if a default value has been assigned to the slot

Should be simple enough.

$slot->default( $instance ) => the default value assigned to the instance. Like value() it could also be an lvalue mutator for scalar slots.

Why would that need to take the instance reference? Surely the default is a single default for the class as a whole?

Another approach is to get rid of has_param and param_name altogether in favor of has_attribute, attribute_name and attribute_value, which could be used not just to "param" but also "reader", "writer" and "mutator" (which would likely come next in my search for metadata to print). And in that case you would have to promote default to an attribute as well, even if one that can also be set via parameters.

I'm not sure I follow this bit.

@garu
Copy link
Owner

garu commented Jul 25, 2021

@leonerd

you can't even know that a default exists. Would you need to know such, for Data::Printer?

I think so, yeah. If you are debugging an instance, it's important to see where each value came from.

Why would that need to take the instance reference? Surely the default is a single default for the class as a whole?

You're right, it doesn't need to take the instance reference. For a second there I mistook slots for roles and assumed they had no idea which classes had them. My mistake :)

I'm not sure I follow this bit.

Oh, this was just me thinking out loud how to check all the other attributes available for has. Currently you have "reader", "writer", "mutator", "weak" and "param". If you wanted to expose those settings on the slot meta class for introspection/debugging (either by DDP or anything else), and if you were to make your current API consistent, you'd have to implement "has_reader" , "reader_name", "has_writer", "writer_name", and so on for all of those. So instead you could just provide a "catch-all" API where instead of:

    $slot->has_param;
    $slot->param_name;

you would have:

    $slot->has_attribute('param');
    $slot->attribute_name('param');

Then I imagined this same API could be used to fetch information regarding default values if they were changed to be attributes too (e.g. "has_attribute('default')"), but of course that would change the syntax from:

    has $x = 0;

to

    has $x :default(0);

which I'm quite confident is not what you want.

So just ignore my ramblings :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants