http://qs1969.pair.com?node_id=11130675

bliako has asked for the wisdom of the Perl Monks concerning the following question:

What happens when a ref is blessed into a non-existent class. Afterall bless's 2nd argument is a string and not a package qualifier.

bless [1,2,3] => 'nonexistent'

Some (irrelevant) info:

my use case is for "tagging" basic Perl data structures.

For example, a sub normally returns an arrayref. But in case of error it returns again an arrayref as: [code,errstr]. How can I differentiate the 2 cases? (Well the obvious way would be to create a proper Error class and return that in case of errors), but just for fun:

sub foo { if( $error ){ return bless [0, "error was ..."] => 'YouveGotError' } return [42,43] } my $ret = foo(); die "@$ret" if ref($ret) eq 'YouveGotError';

Thanks LanX for looking this up on CB and Corion for pointing to %main:: (I was looking in %INC)

2' Edit: here is another use-case for distinguishing between scalar(ref)s

sub fortunecookie { my $cookie = ...; if( ! $cookie ){ my $errstr = "failed to get a cookie because ..."; return bless \$errstr => 'YouveGotError' } return \$cookie } # ok a bit awkward with the stringref but it demonstrates that one can + do this with scalars too

blw bli ako

Replies are listed 'Best First'.
Re: Blessing with unknown classnames
by kcott (Archbishop) on Apr 01, 2021 at 17:35 UTC

    G'day bliako,

    There is no requirement for $class to be a real class, have length, or even be present. See bless for details.

    The use of ref in your posted code is not the best choice: the doco explains issues. It would be much better to use the blessed function of the builtin module Scalar::Util.

    Here's an example of usage. My perle alias captures all sorts of problems.

    $ alias perle alias perle='perl -Mstrict -Mwarnings -Mautodie=:all -MCarp::Always -E +'
    $ perle ' use Scalar::Util "blessed"; for (1, 2) { say "Run: $_"; my $ret = foo(); my $err = blessed $ret; if (defined $err) { say qq{Error "$err" detected}; say qq{Code: $ret->[0]}; say qq{Errstr: $ret->[1]}; } else { say qq{Success!}; say for @$ret; } } sub foo { state $error = 0; my $retval; if ($error) { $retval = bless [0, "error was ..."] => "YouveGotError"; } else { $retval = [42, 43]; } $error ^= 1; return $retval; } ' Run: 1 Success! 42 43 Run: 2 Error "YouveGotError" detected Code: 0 Errstr: error was ...

    [Note: Between reading the OP and clicking "Comment on", there appears to have been two updates to the OP. There may be others. I'm replying to the post that ended with "Thanks LanX for looking this up on CB". I don't believe this changes the essence of my reply; just bear in mind what I'm replying to.]

    — Ken

      Hello Ken,

      Minor issue: finding the classname (e.g. ref($ret)) is part of deciding if sub returned an error, for example when sub normally returns a blessed object instead of unblessed ref (as in my example).

      apropos the edits: Yes, I have added another use-case 5 minutes after posting, sorry. It does not change anything in your post.

        "Minor issue: finding the classname (e.g. ref($ret)) is part of deciding if sub returned an error, for example when sub normally returns a blessed object instead of unblessed ref (as in my example)."

        Sorry, but I don't understand this. My code using blessed clearly distinguished "Error" from "Success". Your line:

        die "@$ret" if ref($ret) eq 'YouveGotError';

        could be rewritten as:

        die "@$ret" if $err eq 'YouveGotError';

        If I change 'blessed $ret' in my code, to 'ref $ret', the output becomes:

        Run: 1 Error "ARRAY" detected Code: 42 Errstr: 43 Run: 2 Error "YouveGotError" detected Code: 0 Errstr: error was ...

        which, I'm pretty sure, is not what you want.

        I suspect we may be talking at cross-purposes, or there's some other misunderstanding; however, I've looked back over our posts, and can't see what the problem might be.

        "apropos the edits: ..."

        No need to apologise. I just wanted to make it clear what I was responding to. There was no intended rebuke or other negativity.

        — Ken

Re: Blessing with unknown classnames (updated x 2)
by LanX (Saint) on Apr 01, 2021 at 16:44 UTC
    > What happens when a ref is blessed into a non-existent class. Afterall bless's 2nd argument is a string and not a package qualifier.

    You don't reveal the answer on purpose?

    The package/class is created.

    you can check by inspecting the %main:: stash, where all packages live

    DB<30> bless [1,2,3] => 'nonexistent' DB<31> p $main::{'nonexistent::'} *main::nonexistent:: DB<32>

    and I think ° it's not only the typeglob but also the stash %nonexistent:: is created, which will result in several hundreds of bytes of overhead

    DB<32> p *{$main::{'nonexistent::'}}{HASH} HASH(0x2e7b830) DB<33>

    So better don't try to automatically bless into millions of pseudo packages ...

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

    PS: According to some statistics this was my 10000st post here ... frightening.

    update

    °) I couldn't find a pure Perl way to check if the HASH slot of a package:: like entry is filled, without autovivifying that package. It's a very quantum-brainf*ck thing, the observation is changing the result.

    Update

    For clarification: using bless to solve the semipredicate problem is totally fine in my eyes.

    Just be aware that the package will exist. And this globally.

    So you should care about a naming convention in your own namespace.

      Run
      perl -we 'bless {}, "A$_" for 1 .. 2000000'
      and what your memory meter.

      map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]
        Nice,

        on my windows 10 box each package seems to eat ~1k of memory:

        perl -we "sub mem{system qq(tasklist /FI \"PID eq $$\"|findstr perl)}; + mem();bless {}, qq(A$_) for 1 .. 1000000;mem()" perl.exe 29356 Console 1 6 +.616 K perl.exe 29356 Console 1 1.353 +.656 K

        and quite the same on my Linux box:

        perl -we 'sub mem{system qq(cat /proc/$$/status | grep ^VmSize)}; mem( +);bless {}, qq(A$_) for 1 .. 1000000;mem()' VmSize: 23768 kB VmSize: 1234168 kB

        Obviously the package is empty..

        perl -MDevel::Symdump -e "bless {},'notexisting';print Devel::Symdump- +>new('noexisting')->as_string" arrays functions hashes ios packages scalars unknowns

        ..holding just the AUTOLOAD

        use strict; use warnings; my %before = %main::; bless {},'nonexistent'; my %after = %main::; foreach my $symbol (sort keys %after) { next if exists $before{$symbol}; local *myglob = $after{$symbol}; if ( defined *myglob{HASH} ) { my %val = %{ *myglob{HASH} }; print "HASH \%$symbol = ( "; while( my ($key, $val) = each %val ) { print "$key=>'$val', "; } print ")\n" ; } } __END__ HASH %nonexistent:: = ( AUTOLOAD=>'*nonexistent::AUTOLOAD', )

        ..but: For various obscure reasons, typeglobs are always created with a Null SV in the SCALAR slot.

        L*

        There are no rules, there are no thumbs..
        Reinvent the wheel, then learn The Wheel; may be one day you reinvent one of THE WHEELS.
Re: Blessing with unknown classnames
by perlfan (Vicar) on Apr 02, 2021 at 05:27 UTC
    In addition to the comments below, not only can you bless a reference to any string (existing package or not); you may also do so with an existing "oop" module; wishing to bypass the constructor. I will do this when writing in unit tests (and/or mocking), for example - particularly when the construct does nothing special and I wish to focus on a specific methods or two. Note, however; other rules do apply; for example if the package has a DESTROY method or AUTOLOAD method defined; these will still get called as you'd normally expect. E.g.,

    # bypass constructor, don't care about stuff done in Targeted::Module: +:new my $sneaky_instantiation = bless {}, q{Targeted::Module}; # now call any method, e.g., for a unit test isa_ok q{Targeted::Module}, $sneaky_instantiation; ok q{expected value} eq $sneaky_instantiation->_internal_method(), q{u +nit test for _internal_method};