Beefy Boxes and Bandwidth Generously Provided by pair Networks
Don't ask to ask, just ask
 
PerlMonks  

Class / package weak association

by dd-b (Monk)
on Jun 07, 2021 at 20:34 UTC ( [id://11133636]=perlmeditation: print w/replies, xml ) Need Help??

I'm old-school, object-oriented programming didn't start to get mainstream until I'd been in the industry 20 years (the roots go back to before my start, of course). And then Perl has a not completely generic approach to objects.

Today I learned, less painfully than I might have, that when I instantiate an object in one program, use Dumper to make it a string, send that to another program, and eval the string, I get an object exactly matching what Dumper saw (I know there are cases where it can't do that, but my situation is simple and that part works). But...the blessed object and the class/package it's associated with don't interlock. So, having forgotten to include that package in the second program, evaling the Dumper string gave me a blessed object (hash, in this case) with all the right members -- but didn't notice that the package was missing, and that therefore all the methods, including accessors, were missing.

Oops!

I didn't take that long to realize what was wrong, even. It's a cleavage line I don't think exists in most other object environments.

No particular point beyond this; I'm not saying this is bad and must be fixed or anything. It's an extra way to screw up (which I exploited :-) ) made possible by the flexibility of Perl's rather ad-hoc approach to objects. "Meditation" is about right; I'm just thinking about this out loud.

I suppose you could make this a slightly harder mistake, but only by making some low-level changes. If a blessed object kept track of whether it was associated with a package of that name, and the information went into the Dumper output, then when the string was evaled, if there was no package of the right name loaded, it could point that out (optional extra parameter to bless, or something?). Huh; my first thought was that it was going to be hopeless, but maybe there are holes in that idea I haven't noticed yet. I wonder if there is code out there that deliberately uses this somehow?

Replies are listed 'Best First'.
Re: Class / package weak association
by Corion (Patriarch) on Jun 08, 2021 at 06:57 UTC

    Looking at the documentation of Data::Dumper, I think you'd be best off by writing appropriate Freezer and Toaster subroutines, but there is $Data::Dumper::Bless which you can set to a subroutine name of your own choosing instead to load the target module:

    #!perl use 5.020; use Data::Dumper; $Data::Dumper::Deparse=1; $Data::Dumper::Bless="my_bless"; package Foo {}; say Dumper bless { name => "foo" } => "Foo"; __END__ $VAR1 = my_bless( { 'name' => 'foo' }, 'Foo' );

    On the receiving side, you can then implement my_bless as:

    sub my_bless( $ref, $class ) { require $class; bless $ref => $class; }

    But again, implementing the appropriate Freezer and Toaster is much saner than using Data::Dumper and eval.

      Yes, this. With the caveat that require $class won't work. Use Module::Runtime or eval "require $class". (I know eval on untrusted input is bad, but you're already doing eval on the Data::Dumper output, so it doesn't make things worse in this case.)

Re: Class / package weak association
by cavac (Parson) on Jun 08, 2021 at 10:57 UTC

    If you think that Perl has not a most generic approach to objects, you'd probably haven't looked into the "huh, this is possible?" corner enough. Take a look at this:

    #!/usr/bin/env perl use strict; use warnings; use Data::Dumper; # Turn a scalar into a blessed object my $foo = 'THIS IS A SCALAR'; my $bar = [qw(THIS IS AN ARRAY)]; my $baz = {'This' => 'Is', 'A' => 'Hash'}; my $scalarobject = bless \$foo, 'This::Is::A::Dummy::Object'; my $arrayobject = bless $bar, 'This::Is::A::Dummy::Object'; my $hashobject = bless $baz, 'This::Is::A::Dummy::Object'; { # Force the This::Is::A::Dummy::Object class to have a print metho +d that stringifies itself ;-) no strict 'refs'; *{'This::Is::A::Dummy::Object::stringify'} = sub{ my ($self) = @_; + print Dumper($self); }; } $scalarobject->stringify(); $arrayobject->stringify(); $hashobject->stringify();

    This blesses a scalar, an array and a hash with a class that we didn't even create beforehand. AFTER it does that, it modifies the class and adds a "stringify" method, which in turn modifies all the objects of that class. Then we stringify all objects with this newly created method. Result:

    $VAR1 = bless( do{\(my $o = 'THIS IS A SCALAR')}, 'This::Is::A::Dummy: +:Object' ); $VAR1 = bless( [ 'THIS', 'IS', 'AN', 'ARRAY' ], 'This::Is::A::Dummy::Object' ); $VAR1 = bless( { 'A' => 'Hash', 'This' => 'Is' }, 'This::Is::A::Dummy::Object' );

    If that is not generic and flexible, i don't know what is.

    SCNR

    perl -e 'use Crypt::Digest::SHA256 qw[sha256_hex]; print substr(sha256_hex("the Answer To Life, The Universe And Everything"), 6, 2), "\n";'
Re: Class / package weak association
by LanX (Saint) on Jun 07, 2021 at 21:34 UTC
    Hm ... my first idea was to serialize the blessing package, than I realized that in most cases packages are already serialized - in a text representation - in the representing module.pm ...

    Of course you could complain that subs could have been added dynamically outside the module.

    So one could think about dumping the STASH ...

    DB<26> sub BLA::foo { print "BLA::foo"} DB<27> use Data::Dumper DB<28> p Dumper \%BLA:: $VAR1 = { 'foo' => *BLA::foo, 'AUTOLOAD' => *BLA::AUTOLOAD };

    ... alas, Data::Dumper won't serialize the sub's body.

    Other serializers allow B::Deparse to hook in ( Data::Dump probably, IIRC? ) , but this source representation is not 100% reliable.

    And that's where - in my humble opinion - your interesting idea hits a wall.

    As long as one can't reliably serialize the code of the subs of a class, one will need to resort to add the class' code manually.°

    While it's possible to introspect in which file and line a sub was defined, this will only help if it was static source and not a dynamically built eval $SOURCE .

    In short: Serializing the methods of a class is very hard in Perl if it has to be fail proof.

    (I was too eager to meditate and too lazy to search, you may want check if CPAN already offers a solution for that :)

    Cheers Rolf
    (addicted to the Perl Programming Language :)
    Wikisyntax for the Monastery

    °) On a side note: JS has a method .toSource() for such introspection, which unfortunately lacks in Perl.

    update

    Anyway ... I'd be interested to know the use case you see. Packages are after all _global_, so resurrecting them might lead to so heavy side effects, that manual interference is needed anyway.

      To expand a bit, the configuration setting is $Data::Dumper::Deparse to make Data::Dumper also dump the (deparsed) source code of code references. But as LanX already said, the source representation is functionally mostly identical but will not necessarily represent the original code identically.

      The interesting part is once you have subroutines closing over common variables, like for example getters/setters:

      my $name = 'foo'; my $package = { getter => sub ( $self ) { return $self->{$name} }, setter => sub ( $self, $value ) { return $self->{$name} = $value } +, }

      You need to dump these at once so that Data::Dumper dumps the reference to the common variable $name as a single variable. This happens whenever your class has autogenerated methods (like with Moo, Moose etc.).

      Depending on how much control you have over the source code of the class to be dumped, this can make things far more hairy than you want.

Re: Class / package weak association
by LanX (Saint) on Jun 08, 2021 at 11:06 UTC
    > If a blessed object kept track of whether it was associated with a package of that name, and the information went into the Dumper output, then when the string was evaled, if there was no package of the right name loaded, it could point that out (optional extra parameter to bless, or something?).

    OK if you only want to be warned if the package doesn't exist, you can also override bless locally inside a package to do the check for you.

    please note that blessing will autovivify the class, i.e. the package TST1:: will "exist".

    use strict; use warnings; use Data::Dumper; my $obj = bless {}, "TST1"; my $str1 = Dumper $obj; my $str2 = q($VAR1 = bless( {}, 'TST2' );); package Bless::Safe; use subs qw/bless/; sub bless ($;$) { my ($obj,$class) = @_; die "Can't bless into non-existent Package $class" unless exists $main::{"${class}::"}; } our $VAR1; eval($str1) or $@ && die $@; # OK eval($str2) or $@ && die $@; # FAILS

      Can't bless into non-existent Package TST2 at d:/tmp/pm/override_bless.pl line 21.

    HTH! :)

    Cheers Rolf
    (addicted to the Perl Programming Language :)
    Wikisyntax for the Monastery

    updates

  • fixed bug in $@ check
  • added prototype

      Yeah, I already saw issues with attempting to really package the class as part of the stringification (all sorts of issues, many already mentioned here, both in general and in my particular use); I was just a little surprised not to be warned that I'd neglected to use the package.

      I was thinking about adding a new function call to do eval-with-bless-checking, hadn't realized I could actually overload bless to verify the package. I don't do any blessing without packages that I remember in this code, so that might be a clean way to fix that.

      At least I've learned something new (didn't realize I could override bless!). I shouldn't be surprised, at this point there's not all that much I can't override in Perl.

        > At least I've learned something new (didn't realize I could override bless!). I shouldn't be surprised, at this point there's not all that much I can't override in Perl.

        Please note that it's not global but "only" on package basis, which is a good thing. Better don't try this in main.

        Cheers Rolf
        (addicted to the Perl Programming Language :)
        Wikisyntax for the Monastery

Re: Class / package weak association
by ikegami (Patriarch) on Jun 09, 2021 at 06:13 UTC

    I propose that the real problem is using Data::Dumper as something else as a data visualizer.

    Seeking work! You can reach me at ikegami@adaelis.com

      Possibly Dumper is the wrong tool here, yeah.

      I looked at JSON and XML, and it looked to me like the overhead was unreasonable for this use, and that I had to use options that made the generated "standard" representation non-standard to do what I needed (which was to convey Perl objects that I created specifically for communicating between these two programs, between these two programs). And it's a pretty personal specialized application, I don't see it really ever being used outside my network. So this looked like the quickest and cleanest way to get it running, the big downside being that it's solidly perl-specific. Which doesn't bother me a bit.

      I was looking for how to format the information (some header information followed by a list of filenames, repeated multiple times) and parse it to separate the "finding the files to display" function from the "generate the PDF showing the image thumbnails and some information" function, and I realized that inventing my own format was stupid and I should use something standard, but when I looked at the obvious "standard" choices they required generating and parsing code to get to what I needed, and I realized that Dumper didn't. In this limited use I'm not scared of evaling the input (which I produced myself and stored in a file in my own directory), and there's nothing approaching the complexity of circular references or anything, nor likely to ever be (just doesn't fit the data model).

        I looked at JSON and XML

        Those are good* formats for exchanging generic data with other applications - especially so when the other applications are not (necessarily) Perl.

        If you are intending to exchange data between only Perl applications then something like Storable (or Sereal) might be more appropriate.

        * for some definitions of "good" ;-)


        🦛

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlmeditation [id://11133636]
Approved by Discipulus
Front-paged by LanX
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others exploiting the Monastery: (6)
As of 2024-04-24 09:41 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found