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

Intro

I would like to premise that I know what OOP is supposed to be for, but in some cases besides its "structural" benefits, it also makes for agile syntax, which IMHO is good. And the other day I felt like pushing the thing somewhat further...

Well, more precisely I was thinking of a situation in which the objects (in the IT acceptation) will represent mathematical objects (generic acceptation.) Think for example of 2x2 matrices: for one thing one may want to ease their construction. In this case it is possible to export the constructor: it's not usual, but then many OO modules are used just instantiating one object at a time, and in this case it may be appropriate, instead.

OTOH, one may also want a very quick access to the individual entries. In this case the natural choice would seem to use a blessed AoA reference to implement these objects. Yet this has all the usual problems of "traditional" objects, including those with inheritance, and I may well want to subclass a matrix class. Basically I would like an agile notation as the one I would get knowing the implementation details, but I also want to avoid to do so at the same time.

Expectations

Expanding on the last paragraph above, ideally what I'd like would be a means to dereference an object in some specific way regardless of the object's implementation itself. In this sense I see that overload can overload dereferencing, but only for the  ${} @{} %{} &{} *{} forms, and not for the arrow operator. Thus what I'd want would rather be a set of especially named methods such as


Update: as pointed out by diotalevi, that is not the case, hence the striked out text. (/me goes thinkering whether the approach I presented here may have some other point of interest, apart that of putting something "exotic" in the blessed ref used for object implementation...)


sub AS_ARRAY { my $self=shift; # do something useful here }

so that

$obj->[42]; # would be the same as $obj->AS_ARRAY(42); # and so on...

Unfortunately, such beasts do not exist. Anyway sub dereferencing is agile enough for me and all of what I need. Thus I also tried putting a suitable sub in the CODE slot of the '' stash entry, but not unexpectedly it doesn't work: when perl sees ->(, it reads "sub dereferencing", not "call of the '' method"...

The idea

Well, I found a "solution" (which is a solution only in quotes because there's not much of an actual problem, just desire to explore syntax and semantics possibilities) which is based, obviously, on blessed subrefs and also takes an Inside-Out approach: the object does contain something, namely a reference to itself, which also builds a closure, but is still used only like an index for property access.

Actually, had not been for a problem I'm reporting below, I would have not posted this under SoPW, but as a meditation, possibly under a title such as "blazar's bizarre OO model".

To explain the basic idea hereafter I'm showing what could be the skeleton of a Matrix class. Please note that this also uses lvalued methods which are a completely debatable, and debated, subject in and of themselves... (But if I'm to get on people's nerves, I want to do it thoroughly! :-)

# -*- Perl -*- use strict; use warnings; package Matrix; use Scalar::Util qw/refaddr/; use Data::Dumper; use base 'Exporter'; our @EXPORT='matrix'; use overload '*' => sub { $_[0]->mult($_[1]) }, '*=' => sub { $_[0]->multby($_[1]) }, '**' => sub { $_[0]->pow($_[1]) }; sub matrix { Matrix->new(@_) } sub mkaoa { [[ @_[0,1] ], [ @_[2,3] ]] } { my %entry; sub entry : lvalue { my $id=refaddr shift; @_==0 ? $entry{$id} : @_==1 ? $entry{$id}[ $_[0] ] : $entry{$id}[ $_[0] ][ $_[1] ]; } sub DESTROY { delete $entry{refaddr shift} } } sub new { my $class = shift; my $self; $self = sub : lvalue { $self->entry(@_) }; bless($self => $class)->init(@_); } sub init { my $obj=shift; $obj->() = mkaoa @_; $obj; } sub _mult { my ($s,$o)=@_; $s->(0,0)*$o->(0,0) + $s->(0,1)*$o->(1,0), $s->(0,0)*$o->(0,1) + $s->(0,1)*$o->(1,1), $s->(1,0)*$o->(0,0) + $s->(1,1)*$o->(1,0), $s->(1,0)*$o->(0,1) + $s->(1,1)*$o->(1,1); } sub mult { my ($s,$o)=@_; matrix $s->_mult($o) } sub multby { my ($s,$o)=@_; $s->() = mkaoa $s->_mult($o); $s; } sub pow { my ($self, $pow)=@_; my $out=matrix 1,0,0,1; $out->multby($self) for 1..$pow; $out; } 1; __END__

Note: I am also aware that this doesn't make for speed nor for memory-friendness either, but I don't care. I do care when... it is worthwile to care. I'm concerned with syntax here.

And you would use it like thus:

#!/usr/bin/perl -l use strict; use warnings; use Matrix; # A somewhat esoteric way to print the # twelfth Fibonacci number. print +(matrix(1,1,1,0)**12)->(1,0); __END__

The problem

There's an obvious problem with the approach above: the objects, as anonymous subs, hold a reference to themselves. Thus when you reach a point where a "normal" object would be DESTROYed, these won't because there's still a reference to them lying around. I'm showing this with a minimal example:

#!/usr/bin/perl use strict; use warnings; package Foo; use Scalar::Util qw/refaddr/; { my %bar; sub bar { my $id=refaddr shift; @_ ? $bar{$id}=shift : $bar{$id}; } sub DESTROY { my $obj=shift; warn "Deleting $obj\n"; delete $bar{refaddr $obj}; } } sub new { my ($class, $type, $data)=@_; my $obj; $obj=bless $type eq 'code' ? sub { $obj } : {} => $class; warn "Creating $obj\n"; $obj->bar($data); } package main; sub test { my $type=shift; warn "--- Testing <$type> ---\n"; my $x=Foo->new($type => 'x'); warn "Leaving context\n"; } test $_ for qw/code other/; END { warn "Leaving program\n" } __END__

This gives me:

--- Testing <code> --- Creating Foo=CODE(0x224eb4) Leaving context --- Testing <other> --- Creating Foo=HASH(0x225bec) Deleting Foo=HASH(0x225bec) Leaving context Leaving program Deleting Foo=CODE(0x224eb4)

Thus, with no interventions leakages are unavoidable. This is by all means undesirable.

I thought it would have been obvious that I could use Scalar::Util's weaken() as a cure, but I played around with it and cannot seem to make the approach to work. Am I missing something obvious? All of my idea may not be such a great contribution to humanity, after all, but I still find it interesting in principle, and I would like to know if at least it can be patched as to avoid that hateful leakage.

Replies are listed 'Best First'.
Re: Agile syntax (abuse?)
by diotalevi (Canon) on Apr 03, 2007 at 17:22 UTC

    I see that overload can overload dereferencing, but only for the ${} @{} %{} &{} *{} forms, and not for the arrow operator.
    You have made a bad assumption. overload hooks the entire dereferencing method regardless of the mere syntax you use. If you have overloaded array dereferencing then all of $o->[42], @$o<c>, <c>@{$o}[42], etc are all affected.

    Further, if you wish to call a method with no name, you'd do it thusly: $method = ''; $o->$method(...).

    ⠤⠤ ⠙⠊⠕⠞⠁⠇⠑⠧⠊

      You have made a bad assumption. overload hooks the entire dereferencing method regardless of the mere syntax you use. If you have overloaded array dereferencing then all of $o->[42], @$o, @{$o}[42], etc are all affected.

      May I dare to say D'Oh?!?

      Further, if you wish to call a method with no name, you'd do it thusly: $method = ''; $o->$method(...).

      Well, that I knew, but of course it's a whole another story.

Re: Agile syntax (abuse?)
by Anno (Deacon) on Apr 03, 2007 at 18:51 UTC
    You wrote:

    Thus what I'd want would rather be a set of especially named methods such as

    sub AS_ARRAY { my $self=shift; # do something useful here }
    so that
    $obj->[42]; # would be the same as $obj->AS_ARRAY(42); # and so on...
    But... that's exactly what arrayref-overloading does. I believe you can base your matrix class entirely on that. You'd access a matrix object $m like this
    @$m # entire matrix, your syntax: $m->() $m->[ 1] # second row, your syntax $m->( 1); $m->[ 1]->[ 0] # individual element, your syntax $m->( 1, 0);
    The syntax is pure Perl. Your's is more compact and closer to conventional mathematical notation. In fact, I like it and will keep it in mind for future matrix work.

    Later you say:

    ...which is based, obviously, on blessed subrefs and also takes an Inside-Out approach: the object does contain something, namely a reference to itself, which also builds a closure, but is still used only like an index for property access.

    Boo! That spoils most of the inside-out fun. Your objects rely on their body being a coderef, never mind how you access properties. Your class is hybrid to begin with and can't easily accomodate (inheritance-wise) a foereign class.

    And it's not necessary either. Subref overloading is powerful enough to make your objects act as if their body were a coderef without it actually being one.

    I have re-worked your class to be a true inside-out class without changing its interface or, I believe, its behavior. You may still prefer the more perlish style direct array overloading gives you and rewrite it that way.

    Anno

    Update: Replaced the Matrix code. Essential change: ->init now does a full initialization instead of leaving part of the job to ->new. Inessential: Replaced ->pow with a more efficient implementation. Also added a stringification method

      But... that's exactly what arrayref-overloading does. I believe you can base your matrix class entirely on that. You'd access a matrix object $m like this

      Yes, yes, yes, diotalevi pointed that out already, I updated the root node, and I'm still D'oh'ing.

      The syntax is pure Perl. Your's is more compact and closer to conventional mathematical notation. In fact, I like it and will keep it in mind for future matrix work.

      Whoa! Although that was somewhat a minor detail in my post, I'm happy that something good is getting out of it and I feel honoured, and I'm not joking!

      It's really all about me being a nut for not having much experience with overload, reading its documentation quickly and making a wrong assumption. Had I read all of it, I would have of course known better.

      Boo! That spoils most of the inside-out fun. Your objects rely on their body being a coderef, never mind how you access properties. Your class is hybrid to begin with and can't easily accomodate (inheritance-wise) a foereign class.

      No, Anno, why do you say so? My objects IMHO do not rely on being coderefs. The implementation of some methods does, but as an option. One should not care about that and only be concerned about the interface anyway, shouldn't she? Indeed the coderef thingy only provides a shortcut that can, not that must, be used. Granted, sub-deref overloading is a much better means to obtain the same shortcut. (If the shortcut itself is part of the interface as opposed to an implementation detail, I suppose using it in derived classes would be legitimate as well.) Anyway, can't you have a derived class with code such as the following?

        Anyway, can't you have a derived class with code such as the following?
        # -*- Perl -*- use strict; use warnings; package Matrix::Named; use Scalar::Util qw/refaddr/; use base 'Matrix'; { my %name;
        Ha! Yes, that works because you have been careful to implement the name field in inside-out manner. The trouble begins when you want to combine the Matrix class with a foreign (non-inside-out) class.

        Say I've picked up your nifty Matrix class from somewhere, and this this lovely Angle class from somewhere else. I want to combine them in a class MyAngle so that matrix operations can be applied to an angle and refer to the corresponding rotational matrix.

        Unfortunately, Angle is implemented in the traditional way as a (scalar) ref to the numeric value.

        With a true inside-out implementation of Matrix, this can be easily done like this:
        #!/usr/local/bin/perl use strict; use warnings; $| = 1; use Vi::QuickFix; # Definition of MyAngle below my $alpha = MyAngle->new( rad => atan2( 1, 1)); printf "Angle: %s (%s deg)\n", $alpha->rad, $alpha->deg; # Angle func +tions print "Matrix:\n$alpha\n"; # show the matrix representation ################################################## package MyAngle; use lib 'lib'; use base 'Angle'; use base 'Matrix'; sub new { my $class = shift; $class->Angle::cre->MyAngle::init( @_); } sub init { my $obj = shift; $obj->Angle::init( @_); $obj->Matrix::init( cos( $_), -sin( $_), sin( $_), cos( $_)) for $obj->rad; $obj; } __END__
        This relies on the possibility to replace the body of an inside-out object, (normally an undefined scalar) with some foreign object of any provenience, (also a scalar in this case, but with significant content) so that its methods work without a hitch. This approach won't work with a hybrid class where the body is required to be code.

        Anno

        PS: If you actually want to run the code against the inside-out implementation of Matrix use the updated code from my previous posting. There was a bug in the original that would have been a show-stopper.

Re: Agile syntax (abuse?)
by Rhandom (Curate) on Apr 03, 2007 at 20:33 UTC
    This seems to work for me.

    use strict; END { print "END\n" }; { package SubRef; use Scalar::Util qw(weaken refaddr); sub DESTROY { my $self = shift; print "DESTROY ".ref($self) ."\n" +} sub new { my $class = shift; my $copy; my $self = $copy = bless(sub { $copy->ok }, $class); weaken $copy; return $self; } sub ok { print "SubRef ok ".refaddr($_[0])."\n" } } { package ArrayRef; @ArrayRef::ISA = qw(SubRef); sub new { my $class = shift; return bless [], $class; } } { package CircularRef; @CircularRef::ISA = qw(SubRef); sub new { my $class = shift; my $s = {}; $s->{self} = $s; return bless $s, $class; } } for (1..2) { my $sub = SubRef->new; my $sub2 = SubRef->new; my $arr = ArrayRef->new; my $cir = CircularRef->new; $sub->(); $sub2->(); } print "After For\n";


    It prints the following:

    paul@paul-laptop:~$ perl foo SubRef ok 135976352 SubRef ok 135976436 DESTROY ArrayRef DESTROY SubRef DESTROY SubRef SubRef ok 135976352 SubRef ok 135976424 DESTROY ArrayRef DESTROY SubRef DESTROY SubRef After For END DESTROY CircularRef DESTROY CircularRef


    I was having issues getting it to work - but then I moved from a one-liner to a program and suddenly the world was fine. I think that the similar action in new would work on your calls also.

    Update - Just to double check I used the following lvalue based subref which worked also.

    my $self = $copy = bless(sub :lvalue { $copy->ok; my $n }, $cla +ss);


    my @a=qw(random brilliant braindead); print $a[rand(@a)];
      I was having issues getting it to work - but then I moved from a one-liner to a program and suddenly the world was fine. I think that the similar action in new would work on your calls also.

      Thank you: in view of the other answers the problem is not so much of a problem any more. Yet I was annoyed by not being able to use weaken appropriately, and you gave me an explicit example about how to put it to work. Thinking of your actual example code, anyway, makes me think that while weaken() is a precious tool, perhaps a more direct interface to the ref count of a reference could be desirable. Perhaps a lvalue refcount() function to be used (mostly) like thus:

      refcount($ref)--;
      Update - Just to double check I used the following lvalue based subref which worked also.

      Well, I wouldn't have expected that to make a difference anyway...