in reply to Re^2: Agile syntax (abuse?)
in thread Agile syntax (abuse?)

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.

package Angle; use strict; use warnings; use constant PI => 4*atan2( 1, 1); use constant RAD => 180/PI; sub cre { bless \ my $angle, shift } sub new { my $class = shift; $class->Angle::cre->Angle::init( @_); } sub init { my $obj = shift; $$obj = _get_angle( @_); $obj; } sub rad { ${ shift() } }; sub deg { ${ shift() }*RAD } sub _get_angle { my $angle; if ( @_ == 1 ) { $angle = shift; } else { my %spec = @_; $angle = exists $spec{ deg} ? $spec{ deg}/RAD : $spec{ rad}; } modulo( $angle, 2*PI); } use POSIX; sub modulo { my ( $x, $y) = @_; $y ? $x - $y*POSIX::floor( $x/$y) : $x; } 1;
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.

Replies are listed 'Best First'.
Re^4: Agile syntax (abuse?)
by blazar (Canon) on Apr 04, 2007 at 19:41 UTC
    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.

    (++) x As::Much->as('possible'); (which unfortunately is only 1) both for the intrinsic educational value of your post and for the nifty example you came up with. Perhaps all this may be the basis for a future tutorial... OTOH I must admit I'm such a blockhead that while I could understand at a glance why all this worked and grasped the beauty of the scheme, initially I could not see what would have gone wrong with "my" implementation, but eventually I did.

    Said this, I know I could find out by experimenting myself and reading the docs, but I seem to notice that "this kinda things" tends to present more possibile gotchas waiting in the shadow to bite you in the neck than one on average would expect, so I'm asking directly: if the implementation is a pure Inside-Out ref-agnostic one and deref overloading is used as in the above, can one reliably use dereferencing "as a shortcut" (supposing that in a realistic case that would be part of a documented public interface) in the derived class implementation?

      ...educational value of your post

      I'm doing this as much for my own edification. I am glad for the opportunity to discuss general principles in the context of a concrete class implementation. If it hadn't been for the context of, specifically, 2x2 matrices I wouldn't have come up with the example of combining a matrix class and an angle class to form a class of rotational matrices. I've been looking for an example that makes some sense of using two classes in tandem, more than attaching a label or a time stamp to anything. I am glad the discussion brought one up.

      ...could not see what would have gone wrong with "my" implementation

      One property of true inside-out classes is that you can supplant their (undefined-scalar) body with any other reference without impeding their function. This is how inside-out classes are able to make one conventional class a base class, by adopting its body. The result is a hybrid class that is no longer able to inherit from another conventional class without the problems of normal Perl inheritance. Your implementation was hybrid to begin with, so it lost the ability to inherit from a conventional class.

      In general, an object can be initialized to any number of inside-out classes, plus one conventional class which determines its body. None of these must be the class the cobject is blessed into.

      To your question: If the implementation is a pure Inside-Out ref-agnostic one and deref overloading is used as in the above, can one reliably use dereferencing "as a shortcut" (supposing that in a realistic case that would be part of a documented public interface) in the derived class implementation?

      I don't quite see why you'd call it a "shortcut", but yes, it should work. That doesn't mean it is generally a good idea.

      Overloading (any overloading, not just references) is a liability to inheriting classes -- you don't get rid of it easily and it's a nasty surprize if a minor contributing ancestor overloads, say, "bool" in unexpected ways. If at all possible, overloading should be optional. That would mean it can't be used in the class implementation itself.

      If overloading is an intrinsic part of the class, there is no reason (except, perhaps, speed considerations) not to use it in the class implementation.

      As to ref overloading specifically, there is always a conflict when you overload the type of reference that the object body actually is. For pure inside-out classes this is irrelevant, but it matters for their potential hybrid subclasses. In other words, if you decide that your class overloads hash dereferencing, and a conventional hash class wants to make you a base class, there's a conflict. The methods of the hash class won't be able to access the body. Similarly there would have been a conflict in my example if the Angle class had happened to be implemented as a class of coderefs.

      The conflict can be avoided by making the overload routine caller-sensitive so that it returns its argument unchanged to all code that isn't compiled in its class. In Matrix.pm, the routine for overloading "&{}"

      sub _code : lvalue { $code{ refaddr shift} }
      should have been
      sub _code : lvalue { my $obj = shift; return $obj unless caller eq __PACKAGE__; $code{ refaddr $obj}; }
      It is easy to forget this little courtesy to potential subclasses. Like you said, the area of overloading, and ref overloading in particular, has more possibile gotchas waiting in the shadow than is immediately apparent. Inside-out techniques add new aspects to the scenario.

      Anno