Beefy Boxes and Bandwidth Generously Provided by pair Networks
Problems? Is your data what you think it is?
 
PerlMonks  

my Dog $spot;

by Zaxo (Archbishop)
on Jun 06, 2006 at 08:05 UTC ( [id://553753]=perlmeditation: print w/replies, xml ) Need Help??

I was poking around in yet another odd corner of Perl, attributes.pm, and I noticed an interesting possibility. According to the pod, the statements,

=pod package Canine; package Dog; my Canine $spot : Watchful ;
should have the effect,
use attributes (); attributes::->import(Canine => \$spot, "Watchful"); =cut
If all is well, &attributes::import calls Canine::MODIFY_SCALAR_ATTRIBUTES(Canine => \$spot, 'Watchful') to handle the attribute bookkeeping.

"Well," I thinks, "Don't that look just like a constructor?"

If MODIFY_SCALAR_ATTRIBUTES() is written as a constructor, then my Dog $spot; could call it on $spot and give us a dog with that purely declarative syntax. So here goes . . .

I'll simplify the example a little bit, making only one canid class,

use warnings; use strict; package Dog; sub MODIFY_SCALAR_ATTRIBUTES { my $class = shift; my $ref = shift; my %dog; @dog{@_} = (1) x @_; $$ref = \%dog; bless $$ref, $class; print "Woof!\n"; (); }
The bark is to let us know that the constructor has been called. This is an experiment, after all, and experiments need instruments.

For a demonstration, we don't need any more methods than that. Time to test drive:

package main; my Dog $spot :Watchful Muddy; my $izzie = ref $spot; print $izzie? "$izzie Spot is @{[keys %$spot]}": "No Spot", $/;
Which faithfully prints:

Woof!
Dog Spot is Muddy Watchful

This is nifty all right, but it turns out to have a problem. I'd like to call the constructor with just,

my Dog $rover;
Perl accepts that, but there's no "Woof!" when it runs. No Rover. Instrumenting a private copy of attributes.pm shows that &attributes::import is never called when the attribute list is empty. That's not what I expected, but I can't find any promises in the docs to say it should. In fact, that's very like the behavior of use with no import list.

The empty attribute list would be no problem for attributes.pm. Its import() function is structured to work with that.

Having that syntax available would make very happy those people who like strict typing, and I don't think it would rock the structure of Perl much. I haven't yet gone source diving to see what's needed to make that happen. Does anybody have a feel for that?

After Compline,
Zaxo

Replies are listed 'Best First'.
Re: my Dog $spot;
by xdg (Monsignor) on Jun 06, 2006 at 14:21 UTC

    I see two problems with that. First, what if you want a typed variable that's empty?

    my Dog $spot; # new Dog my Dog $dog = $spot; # oops - create a Dog just to destroy it

    The other problem I see with making that change is that every use of my winds up with run-time overhead to call attributes::import. If you limit it only to my statements with types, that might be OK, but the more general case would suggest that my $rover would call attributes::import in the current package with an empty list, too.

    In either case, I think it's too much overhead for the sake of some nice object constructor syntax.

    That said, ++ for cool exploration of attributes.pm. That attributes as constructor trick might be useful for quick-and-dirty generic objects with dynamically-generated accessors.

    my Object $soldier : name rank serial_no; $soldier->name( "Damian" );

    -xdg

    Code written by xdg and posted on PerlMonks is public domain. It is provided as is with no warranties, express or implied, of any kind. Posted code may not have been tested. Use of posted code is at your own risk.

      Objection 1: Well, don't do that ;-) Even though my Dog $spot; looks like strong typing, it isn't. Following with my $dog = $spot; accomplishes exactly the same thing without the constructor call, which is . . .

      Objection 2: I agree that the price is too high if allowing the import call for an empty attribute list hurts the performance of vanilla my calls. There appears to be a place in the lexer for my Dog $spot;, and with the death of pseudohashes that ecological niche is empty. Since attributes.pm has already absorbed part of it, it seems natural to extend the rest that way. If the pseudohashes' use for that kind of statement didn't interfere with vanilla my, then it seems likely that this wouldn't either. That remains to be seen, though.

      As for the rest, thanks for the props. I agree that that kind of generic object would be easy to generate this way. I'm not a big fan of that sort of thing, though. Without any methods but getters and setters, those classes just look like hash data to me.

      Let me expand a little about dynamic strong typing. That's what's behind my interest in this little hack.

      The declaration my Dog $spot; doesn't really implement any kind of strong typing. It just looks that way to C++ and Java slingers. A strong dynamic type system for perl needs a little more than that.

      Dynamic strong types require a great deal of expensive monitoring and testing at runtime. They can't rely on static checking, like C++. It should be optional in Perl, and you shouldn't pay for it if you don't use it. You shouldn't use it if you don't need it.

      I'm working on a follow-on to Tie::Constrained, one which will be a base class for dynamic strongly-typed classes of all kinds. Instead of my Foo $foo; making $foo a Foo, it can tie $foo in a way that makes sure it subsequently remain a Foo. Attempts to assign non-Foo's to $foo will meet sudden death (or something).

      That's why I'm interested in this syntactic sugar. The tie interface is ugly and obscure. A constructor-like interface makes people expect a regular weakly-typed object. For a strongly typed Dog class, my Dog $spot; looks exactly like what it does -- makes an uninitialized but indisputable Dog.

      That's what I'm really trying to do. An optional, per-variable, pay-as-you-go strong type system for Perl.

      It can detaint, too.

      After Compline,
      Zaxo

        Let's not forget that while native phash syntax is going/gone, phashes still exist in the form of use fields. IIRC, you still need to use the my Dog $spot syntax if you want compile-time checking of field based classes.

        Ted Young

        ($$<<$$=>$$<=>$$<=$$>>$$) always returns 1. :-)
Re: my Dog $spot;
by TedYoung (Deacon) on Jun 06, 2006 at 13:02 UTC

    Well, the reason why attributes::import is not called in a statement like my Dog $spot is because IIRC the syntax my Class $var was originally created for things like compile-time field checking of psuedohashes. The attribute thing came later.

    Ted Young

    ($$<<$$=>$$<=>$$<=$$>>$$) always returns 1. :-)
      My first "A-ha!" moment of the morning - I have some old code I maintain that does exactly that my Dog $spot; thing, and has pseudo-hashes - now I know the reason ! - I always thought it was because the original author of the code was a Java programmer feeling homesick...

      ...reality must take precedence over public relations, for nature cannot be fooled. - R P Feynmann

Re: my Dog $spot;
by perrin (Chancellor) on Jun 06, 2006 at 16:23 UTC
    I remember Damian Conway had a whole spiel about why this syntax was bad and shouldn't be used. With pseudo-hashes now removed, I think this is just a leftover from a failed idea.

      I can't comment on Damian's spiel; I've never heard it. I'd appreciate a link if he's published it somewhere.

      I think you're right about leftovers, and that's encouraging. An empty niche in the lexer ecology is just begging to be filled. It wouldn't be there if it interfered too much with vanilla my declarations (per xdg's second objection, above).

      After Compline,
      Zaxo

Re: my Dog $spot;
by Zaxo (Archbishop) on Jun 08, 2006 at 05:10 UTC

    Here's a Dog class which admits dynamic strong typing/value types, like I suggested in my replies to xdg and TimToady.

    package Dog; use Tie::Constrained; use Scalar::Util qw/blessed/; # constraint object my $is_a_dog = Tie::Constrained->new( sub { blessed $_[0] and $_[0]->isa('Dog') }, ); sub new { bless {}, shift; } # not a constructor this time, ties the referent sub MODIFY_SCALAR_ATTRIBUTES { my $class = shift; my $ref = shift; tie $$ref, 'Tie::Constrained', $is_a_dog; # ignore attributes for this example, but # must have one to get called (); } sub bark { my $self = shift; print "Woof!\n"; } package Cat; sub new { bless {}, shift; } package main; my Dog $spot : Generic ; eval {$spot->bark} or warn q(Only tangible Dogs bark, ), $@; eval {$spot = Cat->new} or warn q(Can't be a Cat, ), $@; eval {$spot = Dog->new} or warn q(Major malfunction: ), $@; eval {$spot->bark} or warn q(Only tangible Dogs bark, ), $@;
    Which prints,

    Only tangible Dogs bark, Can't call method "bark" on an undefined value at dog.pl line 42.
    Can't be a Cat, Constraint violation: at dog.pl line 44
    Woof!

    Declared attributes would likely be stored in %{tied($spot)}, which is persistent and independent of whatever is assigned the name. Matching declared attributes with Dog instance ones could be part of the constraint.

    This is a very rough proof of concept. I haven't tried to make my $spot : Watchful = Dog->new; do the right thing. I haven't checked if it plays nice with fields.pm or Attribute::Handlers. I'd be surprised if it did at this stage.

    After Compline,
    Zaxo

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others romping around the Monastery: (3)
As of 2024-04-18 23:12 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found