Debugging Corinna Objects

Perl 5.38 brought a new syntax for object oriented programming in Perl with the Corinna project as a new feature 'class' . One of its characteristic features is encapsulation: Unlike objects implemented as blessed hash or array references, there is no "low level" way to access the fields of a Corinna object. This turns out to be an obstacle for diagnostic software which use these low level access to show an object's internals. Data::Dumper and the Perl debugger are bundled with Perl, but ... not very useful for Corinna objects.

I am not considering Data::Dumper here: It has the extra challenge that its output format is valid Perl code. I do not need my debugging information to be presented as valid Perl code.

Code Example

The following example (download) defines a 3D "box", following the (simplified) construction of a box in Extensible 3D (X3D). The size parameter is a reference to an array giving the extensions of the box in x, y and z direction. The Perl object also holds a list of the vertices of the box, and a list of lists which defines which of the vertices define the faces of the box.

use 5.038;
use feature 'class';
no warnings 'experimental';


class Object {
    field @vertices;
    field @faces;

    ADJUST {
        @faces    = $self->init_faces;
        @vertices = $self->init_vertices;
    };
}


class Box :isa(Object) {
    no warnings 'experimental';

    field $size :param = [2,2,2];

    method init_vertices {
        my ($x,$y,$z) = map { $_ / 2 } @$size;
        return ([-$x,-$y,-$z],  # 0
                [ $x,-$y,-$z],  # 1
                [-$x, $y,-$z],  # 2
                [ $x, $y,-$z],  # 3
                [-$x,-$y, $z],  # 4
                [ $x,-$y, $z],  # 5
                [-$x, $y, $z],  # 6
                [ $x, $y, $z]); # 7
    }

    my @init_faces = ([0,2,3,1],[0,1,5,4],[0,4,6,2],
                      [1,3,7,5],[2,6,7,3],[4,5,7,6]);
    method init_faces {
        return @init_faces;
    }
}


my $box   = Box->new(size => [4,9,1]);
$DB::single = $DB::single = 1;
say 'At the debugger prompt, say "x $box <RETURN>"';

After creation of the box, the program does nothing but prepare a break point for the debugger. At this point I want to inspect the box, for example to check the coordinates of the vertices. For this demonstration, run it like this with Perl 5.38 or newer:

PERLDB_OPTS="NonStop" perl -d debug.pl
Then, at the debugger prompt, enter:
x $box
The output looks like this:
  DB<1> x $box
0  Box=OBJECT(0x60119185fcf8)
  DB<2>
	

This is not helpful. For comparison, if I implement the same class in Moose (download) and run the same demo, the output looks like this:

  DB<1> x $box
0  Box=HASH(0x5d5621440540)
   'faces' => ARRAY(0x5d56228d1c38)
      0  ARRAY(0x5d56211a7630)
         0  0
         1  2
         2  3
         3  1
      1  ARRAY(0x5d56211aeff8)
      [... several elements skipped for brevity)
      5  ARRAY(0x5d56211af3d0)
         0  4
         1  5
         2  7
         3  6
   'vertices' => ARRAY(0x5d56228c9b98)
      0  ARRAY(0x5d56211af688)
         0  '-2'
         1  '-4.5'
         2  '-0.5'
      1  ARRAY(0x5d56228be0b8)
      [... several elements skipped for brevity)
      7  ARRAY(0x5d56228ca660)
         0  2
         1  4.5
         2  0.5
  DB<2>
	

This is the information I want with Corinna objects, too. I am aware that it isn't as easy as "dumping a hash reference" because in Corinna a parent class (like Object in the example) and a child class (Box in the example) can use the same field names without collision. So Corinna objects need a different output format. But this is not Data::Dumper, there are no conditions our output format must meet.

How it could look like

I can use Object::Pad to demonstrate what I would like to see. Simply replace the second line of the example program by this (download):

use Object::Pad;

Running the same procedure as above gives the following output:

  DB<1> x $box
0  Box=ARRAY(0x5847c28066c0)
   0  ARRAY(0x5847c2806bd0)
      0  ARRAY(0x5847c2447808)
         0  '-2'
         1  '-4.5'
         2  '-0.5'
      1  ARRAY(0x5847c2806258)
      [... several elements skipped for brevity)
      7  ARRAY(0x5847c2630480)
         0  2
         1  4.5
         2  0.5
   1  ARRAY(0x5847c2806738)
      0  ARRAY(0x5847c24356c0)
         0  0
         1  2
         2  3
         3  1
      1  ARRAY(0x5847c2447760)
      [... several elements skipped for brevity)
      5  ARRAY(0x5847c243d5c8)
         0  4
         1  5
         2  7
         3  6
   2  ARRAY(0x5847c243d658)
      0  4
      1  9
      2  1
  DB<2>
	

This is halfway helpful: The names of the fields are not provided, and we also know that this output is just an artifact of how Object::Pad is implemented.

But this can be improved: Object::Pad provides a meta object protocol (MOP) which allows to access the names of the field. All that needs to be done is to use the MOP in the debugger.

And this is what I have done. The Object::Pad debugger is a drop-in replacement for the Perl debugger which uses the MOP to look into objects. Applied to the demo code:

PERLDB_OPTS="NonStop" perl -d:opaddb i/debug_opad.pl
I get the following output:
  DB<1> x $box
0  Box=ARRAY(0x63afe81ebc58)
    -> Object::Pad object with 1 field(s):
      field $size = ARRAY(0x63afe81e1718)
      0  4
      1  9
      2  1
    -> extends Object    -> Object::Pad object with 2 field(s):
      field @vertices = ARRAY(0x63afe8491260)
      0  ARRAY(0x63afe8491818)
         0  '-2'
         1  '-4.5'
         2  '-0.5'
      1  ARRAY(0x63afe81fb598)
      [... several elements skipped for brevity)
      7  ARRAY(0x63afe8832b38)
         0  2
         1  4.5
         2  0.5
      field @faces = ARRAY(0x63afe8490b28)
      0  ARRAY(0x63afe81d9920)
         0  0
         1  2
         2  3
         3  1
      1  ARRAY(0x63afe81e17d8)
      [... several elements skipped for brevity)
      5  ARRAY(0x63afe81e1688)
         0  4
         1  5
         2  7
         3  6
  DB<2> 	
I want something like this for Corinna objects and I am willing to make the necessary changes to the Perl debugger if I have a chance to get at the data.

The code to present Object::Pad objects uses Object::Pad::MOP::Class and its method fields, roles and superclasses, and for the fields the method value.

I have not created an interface to change the values of fields. The debugger itself does not have an interface to change data. You can change data by entering Perl statements. This is good enough to manipulate the contents of Moose objects, but as we know, fields of Object::Pad or Corinna objects can not be changed outside their class (unless there's a method to do so).

See Also

A question on Stackoverflow:
How do you dump an object instance with Perl's new class feature?

Author and Copyright

Copyright 2024 Harald Jörg, <haj@posteo.de>. No rights reserved.