Beefy Boxes and Bandwidth Generously Provided by pair Networks
Welcome to the Monastery
 
PerlMonks  

The Accessor Heresy

by Roy Johnson (Monsignor)
on Nov 28, 2005 at 16:26 UTC ( [id://512251]=perlmeditation: print w/replies, xml ) Need Help??

Disclaimer:
It is important to be aware that I am neither steeped in OO theory nor have I amassed a great deal of experience from which I can speak with authority. Having limited exposure to what's established means I might have fresh perspective, but I might also just fall right into a pit. Only an innocent child could get away with such blasphemy. God bless them all. Amen..

Having devoted more time to meditation and Thanksgiving on the topic of accessors, I believe I understand what it is that makes accessors rankle so much. The common practice of having setters and getters is not an OO practice. It's a hybridized thing that, like many such practices, is probably a pragmatic decision to reduce overhead compared to doing it "the right way".

In OO, things that you interact with are supposed to be objects, and you interact via their methods. Accessible properties are things that you interact with, but they often aren't implemented as objects, and their methods are owned by their parent objects.

Let's consider a Circle object. It's got properties like Area, Radius, and Circumference. (Interestingly, they're all different views of the same fundamental property.) The usual approach might be to have methods setArea, getArea, setRadius, etc. Notice that those names look like classical procedural programming names: what they operate on is part of their name, rather than being the object that owns the method.

An OO approach would be to have the properties be "sub-objects", and the accessors would be called like $myCircle->Area->set(5). You can see that Area could be either the sub-object itself (see tie for Perlish, encapsulated objects), or a function returning the sub-object, depending on your philosophy. Once I started thinking of properties as sub-objects, I found myself preferring method-returning-object over a distinct syntax for returning properties.

Now, since we've got three sub-objects with the same interface (set of methods), it seems reasonable to have a virtual class that defines that interface: a setter-getter scalar class. As it happens, such a virtual class is already defined for us by Perl. It's the scalar class we can tie to. However, the more I thought about it, the less useful it seemed to actually provide a tie-able interface to properties. It was just an interesting side-note.

Update: Well, I had included some example code, but a fat-fingered update ended up deleting it. Probably just as well; it distracted people from the point more than it helped them understand it. The point is: there is a more properly OO approach, but it involves some programming and performance overhead.

holli offers a very nice example below.

Replies are listed 'Best First'.
Re: The Accessor Heresy
by Ovid (Cardinal) on Nov 28, 2005 at 16:44 UTC

    There are a few folks who argue that accessors are bad, objects shouldn't be glorified structs, multiple inheritance is always evil, etc. The main issue I have with statements such as these as they tend to be "holy pronouncements" that some folks take as gospel even though they're just good rules of thumb which should be violated as needed.

    The primary benefit I see in considering such pronouncements is thinking about why they're beneficial, something you're doing. Accessors can violate encapsulation. Glorified structs are often overkill. Problems with MI are extremely well-documented. Once we understand why a particular practice is good or bad, then we're much better prepared to either adopt or reject a given practice for a given need. No blanket pronouncements for me!

    Cheers,
    Ovid

    New address of my CGI Course.

Re: The Accessor Heresy
by Zaxo (Archbishop) on Nov 28, 2005 at 17:23 UTC

    A few years ago, it was easy to get into immense flamewars with OO guys over a Circle class and its relation to an Ellipse class. It's probably just as easy now, but I quit paying attention. "Which inherits from which?" was often the point of contention.

    The OO'ers would contend that Circle is the base class because it contains less data. The mathematicians would scoff, pointing out that a circle is a degenerate case of an ellipse.

    It seemed to me that the OO example Circle class was chosen without enough domain knowledge, and that the mathematicians did not appreciate the need to save one number's worth of storage.

    The OO is-a rule was ignored by the OO crew. Any circle is-an ellipse, but not every ellipse is a circle. That makes Circle a specialization of Ellipse, hence a subclass of it. It's uncommon in ordinary CS problems for a specialization to require less data than the base class.

    There, I've gone and whacked that ol' hornet's nest again ;-)

    P.S. Accessors are a fine thing when there is inheritance and method implementations in the base class.

    After Compline,
    Zaxo

      It's uncommon in ordinary CS problems for a specialization to require less data than the base class.

      Really? That doesn't seem to be my experience...

      Off the top of my head:

      Graphics: Square isa Rectangle isa Polygon Line isa Curve Circle isa Elipse isa Curve

      Data Types: PrimeNumber isa Unsigned Int isa Int isa Real isa Complex String isa ArrayOfObjectTypeCharacters

      Unix I/O: TTY isa CharacterDevice isa IODevice isa FileIOObject (TTY is much simpler than generic character devices, and character devices are much simpler than generic I/O devices. FileIOObjects are a simpler abstraction, however...)

      In general, I find that base classes are usually often more general (and more complex) than specializations, since the point of a generalization is to encompass a wider number of possibilities. General algorithms are more abstract, but specific algorithms tend to more efficient. For example, it takes only two points to specify a line; but an infinite number of points are required to specify a general abstract curve. If you use a Curve object where a Line would do, you've used infinitely more storage space than necessary. ;-)

      I've never been a fan of OO, largely because of code like this:
      @abc=$x->$y(@z);

      It's now so abstract that the code doesn't actually tell me what method is being called, what class that method comes from, and what data is being passed into the (unknown) method. It's all decided at run time; so the only way to know what it will do is to run it, and see what data is reaching that section of the code. That's no fun.

      --
      Ytrew Q. Uiop

        Square isa Rectangle isa Polygon

        Not quite. Square is both a Rectangle and a Rhombus, both of which are Parallelograms which isa Quadrilateral which isa Polygon. In addition, Square->is_regular_polygon() is true where none of the other classes mentioned in this listing can make that claim. is_regular_polygon() would be a method of Polygon.


        My criteria for good software:
        1. Does it work?
        2. Can someone else come in, make a change, and be reasonably certain no bugs were introduced?
Re: The Accessor Heresy
by brian_d_foy (Abbot) on Nov 28, 2005 at 19:54 UTC

    Sometimes it bothers me that objects are fancy structs and at those times I think every property should change indirectly as the result of some action. The properties change because the object does something, not because we simply change the properties. At those times I also think that objects should be little black boxes where we don't know what properties it has, although we can send it messages and get responses.

    But then, I get over it pretty quickly because I don't care that much. It's a nice thing to think about as I design interfaces, but I'm not going to lose sleep over it if I can't make it happen. :)

    --
    brian d foy <brian@stonehenge.com>
    Subscribe to The Perl Review
Re: The Accessor Heresy
by gjb (Vicar) on Nov 28, 2005 at 16:40 UTC

    This may be an academic example, but it's very weird to see you set the area of a circle. Personally I'd have a constructor that takes two arguments or three arguments: either a point or two coordinates (this is open for debate) and the radius of the circle. I'd have a getter for the point and the radius, as well as getters for the area (which would be computed from the radius) and such other properties. Point may be an object if necessary, but it could also be coded as an x- and y-coordinate.

    I'd (if necessary) have methods such as move to change the coordinates of the midpoint and scale to change the radius.

    This seems to me the way to manipulate a circle without fussing with its implementation and that is after all one of the goals of OO.

    Just my 2 cents, -gjb-

    Update: Apparantly I should clarify a bit. The points I'm trying to make with my casual description of a Circle class are (at least ;) the following:

    • Getters/setters don't necessarily come in pairs and
    • you can manipulate an object other than by setting some of its properties directly
    The former point is illustrated by the area example, the latter by the move method which could have been done just be modifying the coordinates of the midpoint.

    As a last point: I don't care whether Radius is ann object or not since I'm not going to manipulate it directly in my example anyway.

      This may be an academic example, but it's very weird to see you set the area of a circle.

      I don't see that as weird at all. It's just the other side of the equation. If it seems weird to you, I'd guess that's because your math classes only taught you to think in one direction. If you'd seen R = sqrt(A/pi) printed inside the back cover of high school algebra textbook, this might not seem so strange.

      -sauoq
      "My two cents aren't worth a dime.";
      

        The point I'm trying to make is that certain properties are derived from others, and while you can go in both directions, there's often a good point not to do so. I agree that there are without any doubt some circumstances in which you'd determine the radius from the area, but this is irrelevant to the discussion.

        My point is that getters and setters very often don't come in pairs.

        I hope this clarifies things a bit, -gjb-

Re: The Accessor Heresy
by dragonchild (Archbishop) on Nov 28, 2005 at 16:48 UTC
    Here's the question you have to ask yourself - how often do you expect to be changing the values of an attribute? Personally, when I create a circle, I don't expect to be changing its radius. I definitely don't expect to set its area. I already created the damn thing - why should I have to tell it how big it is?? It has eyes and a brain - it should tell me! Maybe something like this would be more to your liking:
    package Circle; my $PI = 3.1415926; sub new { my $class = shift; my ($radius) = @_; return bless { radius => $radius }, $class; } sub radius { my $self = shift; return $self->{radius}; } sub area { my $self = shift; return $PI * $self->radius ** 2; } sub stretch { my $self = shift; my ($amount) = @_; $self->{radius} += $amount; return; }
    You'll note the stretch() subroutine - that allows you to alter the size of the circle. But, instead of re-setting the radius, you're $cicle->strech( 3 )'ing, which makes more sense from a reader's point of view.

    Most accessors in Perl are there for the object itself, not for the client. In Ruby, for instance, I would write that Circle class as so:

    class Circle attr_reader :radius @@PI = 3.1415926 @@PI.freeze def initialize ( r = 5 ) @radius = r end def area @@PI * @radius * @radius end def stretch ( amount = 1 ) @radius += amount end end

    @x is an instance attribute and @@X is a class attribute. freeze() marks it as a constant. The ( x = 1 ) in the method declarations are default values.


    My criteria for good software:
    1. Does it work?
    2. Can someone else come in, make a change, and be reasonably certain no bugs were introduced?
      I already created the damn thing - why should I have to tell it how big it is??
      (My comment here may be too specific for the general question. If I'm off base, someone poke me gently.)

      Suppose you have a circle, and now its area needs to be changed. Do you compute the corresponding radius, or do you just set the area, and let the circle compute the radius?

      Suppose it's not Euclidean geometry, but spherical. The circle object should already be instantiated with the geometry parameters (degree of space curvature). You can set the area, and the radius and circumference are updated for you. (Note that in spherical geometry, you can have a radius and circumference of zero and still have a positive area.)

      I guess it depends on what the object's capabilities are, and whether you want to derive the settable attribute yourself (radius based on area), or have the object tell you.

      -QM
      --
      Quantum Mechanics: The dreams stuff is made of

        Suppose you have a circle, and now its area needs to be changed.

        That's a requirement that would mean a new feature. Something like:

        # Perl: sub set_area_to { my $self = shift; my ($area) = @_; $self->{radius} = sqrt( $area / $PI ); return; } # Ruby def area= (new_area) @radius = Math.sqrt( new_area / @@PI ) end # Note, this means that the Ruby version allows for: # circle.area = 22 # And it will DWIM

        I fail to see the problem.


        My criteria for good software:
        1. Does it work?
        2. Can someone else come in, make a change, and be reasonably certain no bugs were introduced?
      Considering that this class exists in a vacuum, I don't think it's valid to comment on what functionality might be useful. Is it so inconceivable that someone might want a circle that covers a specified amount of area that the notion eclipsed the topic of the meditation?

      Caution: Contents may have been coded under pressure.
        I didn't explain myself well enough. The point is that accessors are poor design - they are telling you that you haven't sufficiently thought through the interface.

        Let's put it another way: If you expose your attributes through the use of accessors, you have coupled your client to your implementation. Yes, you can have fake accessors like your Area accessor, and that can be a powerful tool. But, I think it's a poor way to have people think about your object.

        I strongly urge you to take a look at Ruby's Array, Hash, String, and File classes. That is the standard I am now holding myself to when it comes to class design.


        My criteria for good software:
        1. Does it work?
        2. Can someone else come in, make a change, and be reasonably certain no bugs were introduced?

      I'm with Roy Johnson and QM here. You, like gjb, just seem to have a preconceived notion of what a circle object should do and be.

      -sauoq
      "My two cents aren't worth a dime.";
      
        No, I have an initial implementation of a circle object with an example of how that implementation can be extended without ever giving outsiders access to the attributes.

        My criteria for good software:
        1. Does it work?
        2. Can someone else come in, make a change, and be reasonably certain no bugs were introduced?
Re: The Accessor Heresy
by perrin (Chancellor) on Nov 28, 2005 at 17:30 UTC
    That's the wrong direction. Having "Area" be a contained object only makes it more obvious to the caller that this is setting a variable. The caller is not supposed to care about that. A method called "set_area" should be able to set a simple property, or set several properties and call some calculation methods, or make a SOAP call, or whatever. That sort of abstraction is the main reason to use OO.
      You're suggesting that there are no such things as conceptual properties. You are wrong. Calling set_area should conceptually set the area of whatever object it is. And that "area" that is being set is a thing, which (in OO programming) means you implement it as an object.

      Furthermore, the Area->set method of a sub-object can do absolutely anything the set_area property can do. It is every bit as abstract, it just corresponds better to the suggested model. Notice that in my example, both Area and Radius operate on the radius value. They do not have their own independent set of data; the radius value is owned by the Circle object.


      Caution: Contents may have been coded under pressure.
Re: The Accessor Heresy
by lhoward (Vicar) on Nov 28, 2005 at 21:01 UTC
    My problem with this approach is the disregard for the Law of Demeter. In short: Only talk to your immediate friends.

    The Law of Demeter is intended to encourage shy, decoupled code (Pragmatic Programmer p.138) to promote maintainability & adaptability. Without the Law of Demeter, objects depend more on the internal structure of other objects. The trade off is that you end up writing more accessor methods.

    In this example, if Circle::Radius or Circle::Area were changed (or replaced by other objects) interface compatibility must be maintained, not only for Circle but for all the other code that works with Circle objects. If the Circle::Radius & Circle::Area objects are accessed through accessor methods of Circle, then their interface details are hidden from any users of the Circle class.

    Of coruse, any tenet of programming if taken too far leads to horrible code.

    L

      The reason the Circle::Radius object exists is that it is part of the Circle object model. It is not an implementation detail, any more than Circle itself is. It is the portion of the Circle public interface that deals with radius-related property fiddling. Instead of embedding the name of a property in the method name, you would have an object by that name that has a simple method name. For example, you prefer:
      $myCircle->operate_on_Radius;
      Inherent in that name is the fact that there is a Radius property, a sort of implicit object. All else being equal, I would prefer to see
      $myCircle->Radius->operate;
      If either of us got rid of the concept of Radius, we would have to change our API. There is no benefit of either API over the other as far as that goes. If you change the object model, you have to change the API. Because the API is supposed to reflect the object model.

      Caution: Contents may have been coded under pressure.
Re: The Accessor Heresy
by sauoq (Abbot) on Nov 28, 2005 at 17:00 UTC

    I think you've just started to create some useless abstractions. Even in a pure OO language, this wouldn't make much sense at all; instead, Area and Radius properties would be some sort of number objects. In Ruby, for instance, they'd probably be Floats.

    The only advantage is a better-organized object.

    Sorry, but I don't see any better organization here at all. In fact, it seems quite a bit messier to me. You've broken the KISS principle entirely. I understand you are just musing here... but I highly suggest you don't write any actual code like this. Your maintainers will hate you for it.

    -sauoq
    "My two cents aren't worth a dime.";
    
      Area and Radius properties would be some sort of number objects
      Well, they are. I mentioned that you could tie them, if you want to be able to manipulate them more like native numbers. But it's just about as easy to get the value, manipulate it, and put it back. This is exactly the same thing you'd do with parent-level getters and setters.

      I don't see any better organization here at all. In fact, it seems quite a bit messier to me
      In my view, having getFoo, setFoo, doSpecialThingWithFoo, etc. is analogous to having $var1, $var2, $var3 instead of @var. Having like things grouped together under one heading is the improved organization I'm talking about.

      I would be interested to see what you consider to be a maintenance nightmare about the interface. If your beef is with the implementation, well, that's always subject to refactoring.


      Caution: Contents may have been coded under pressure.
        I would be interested to see what you consider to be a maintenance nightmare about the interface.

        There's nothing all that onerous about the interface but the interface isn't what gets maintained; the implementation is.

        In one fell swoop—and with an extremely simplistic example no less—you've almost doubled your number of methods and tripled your number of classes. Your number of lines of code has probably grown in proportion to those numbers... somewhere between one and two hundred percent. With that comes more chance for errors and a bigger haystack to find them in. On top of that, you've given yourself the choice of keeping those classes and methods in separate files or living with multiple methods with the same name (get, set, new) all in one file. All at the expense of performance and for what purpose again?

        -sauoq
        "My two cents aren't worth a dime.";
        
Re: The Accessor Heresy
by gaal (Parson) on Nov 28, 2005 at 17:33 UTC
    One of the huge practical motivations for using accessors in Perl 5 was not encapsulation at all, but reducing typo bugs on field names. Since the mainstream method for fields is still straight hash elements, those can happen a lot.

    If you use fields or employ another technique (I see you're familiar with some of those), that motivation goes away.

Re: The Accessor Heresy
by Aristotle (Chancellor) on Nov 29, 2005 at 04:11 UTC

    But… what’s the the point? And… what are you saying? I don’t understand.

    Are you suggesting that all properties are objects in their own right, if you squint at them the right way, so you should make them all objects? Then that would be architecture astronautics.

    Or are you suggesting that an object which, in addition to the rest of its interface, has do_foo_with_bar, do_baz_with_bar, do_quux_with_bar, do_qux_with_bar, put_bar_in_frobnitz and take_bar_from_frobnitz should instead have-a Bar instance with the respective do_foo, do_baz etc methods? Then that would be plain old good OO design, and not much to make a commotion out of, assuming you don’t layer too deeply and paint yourself in a Demeter-ish corner.

    Your example does nothing to help your argument. A Circumference is not an abstract concept that exists in a vacuum. It is always associated with a closed curve of some sort, and so are Radius and Area. Further, these properties are not independent of each other. If I saw your interface in real code I’d consider it horrible design.

    Your example seems to suggest the architecture astronautics interpretation. That means you are either actually wasting your time thinking about abstractions of abstractions, or you picked a really awful example that doesn’t illustrate anything useful.

    Makeshifts last the longest.

accessor traits (Re: The Accessor Heresy)
by holli (Abbot) on Nov 29, 2005 at 10:35 UTC
    I like the idea of having Objects as accessors. It struck me that traits could be of use here. Here are two traits: The Getter:
    package Class::Trait::TGetter; use strict; use warnings; use Class::Trait 'base'; sub get { my $self = shift; return $self->{object}->{$self->{property}}; } 1;
    and the Setter:
    package Class::Trait::TSetter; use strict; use warnings; use Carp; our $VERSION = '0.03'; use Class::Trait 'base'; #this doesnt work as expected #our %OVERLOADS = ( '=' => "set" ); sub set { my $self = shift; my $value = shift; if ( my $sub = $self->{validate} ) { if ( &$sub ($value) ) { #print "assign $value to $self->{property}\n"; $self->{object}->{$self->{property}} = $value; } else { croak "Illegal value assigned for property $self->{propert +y}"; } } else { #print "assign $value to $self->{property}\n"; $self->{object}->{$self->{property}} = $value; } } 1;
    These are be "consumed" by a GetterSetter (There could also be a pure Getter and a pure Setter) class:
    package GetterSetter; use strict; use warnings; use Class::Trait ( 'Class::Trait::TGetter' => {}, "Class::Trait::TSetter" => {} ); sub new { my $class = shift; my $self = {object=>shift, property=>shift, validate=>shift}; return bless $self, $class; } 1;
    This class can be used by other classes as accessor:
    package Person; use warnings; use strict; use GetterSetter; sub new { my $class = shift; my %args = @_; my $self = bless {}, $class; $self->age->set ( $args{age} || 0 ); $self->name->set ( $args{name} || "" ); return $self; } sub age { my $self = shift; # return GetterSetter with validation return GetterSetter->new($self, "age", sub { return $_[0] =~ /^[0- +9]+$/ }); } sub name { my $self = shift; # return GetterSetter without validation return GetterSetter->new($self, "name");; }
    Now the Person class can be used
    use Person; eval { my $p = Person->new(name => "holli", age => "30"); print $p->name->get, ", ", $p->age->get, "\n"; }; eval { my $p = Person->new(); $p->name->set("holli"); $p->age->set(30); print $p->name->get, ", ", $p->age->get, "\n"; }; #this croaks "Can't modify non-lvalue subroutine call at..." #but shouldn't because the setter should be overloaded #Can you jump in [Ovid]? eval { my $p = Person->new(); $p->name = "holli"; $p->age = 30; print $p->name->get, ", ", $p->age->get, "\n"; }; print $@; #croaks "Illegal value assigned for property age..." eval { my $p = Person->new(name => "Santa Claus", age => "unknown"); print $p->name->get, ", ", $p->age->get, "\n"; }; print $@; #croaks "Illegal value assigned for property age..." eval { my $p = Person->new(); $p->name->set("Santa Claus"); $p->age->set ("unknown"); print $p->name->get, ", ", $p->age->get, "\n"; }; print $@;
    Thoughts?


    holli, /regexed monk/
      Sweet! An OO interface where you don't have to roll everything by hand. This actually makes it practical to implement. I have not yet looked much at traits, but I am encouraged to do so.

      Caution: Contents may have been coded under pressure.
Re: The Accessor Heresy
by radiantmatrix (Parson) on Nov 28, 2005 at 22:41 UTC

    I think you might be over-thinking the nature of objects. I agree that working with object properties using set/get pairs is a bit of laziness; sometimes laziness is good, but I do think the meme is over-used.

    Having thought about your Circle class (and ignoring all the proper inheritance tree that should really exist, etc.), I've come up with my own to illustrate what I'm about to attempt explaining.

    To me, an object is a set of data (attributes) that can be acted upon. When I design an object class, I try to think about what data is really an attribute of the object I'm representing, what can be derived (and/or normalized), and how I'm going to act on that data. The interface should focus heavily on the latter: actions performed on the object's data. The data should be as hidden as possible.

    In the case of our simple conception of a Circle, we only really need to store one attribute, since the other things we're interested in can be derived from it. I chose the radius, since that seems to make sense to me. When we create a new instance of Circle, we know at least one property of that circle, so the constructor should be able to normalize that property to a radius. What do we want to do with our circle? Well, we could grow or shrink it, and we want to learn what its properties are.

    The latter operation is the key: how do we access the properties? It seems to me that asking for a property is one type of operation, and therefore one method. What kind of property we want to learn of is a parameter. No need for get/set pairs, here -- just a single learn method

    package Circle; use strict; use warnings; use constant ATR_RADIUS => 0; use constant Pi => 3.14159; sub new { my $self = bless [], shift; $self->redefine(@_); return $self; } sub redefine { my $self = shift; my ($prop, $value) = @_; my $radius = 0; if ($prop eq 'radius') { $radius = $value; } elsif ($prop eq 'area') { $radius = sqrt( $value / Pi ); } elsif ($prop eq 'circumference' or $prop eq 'circum') { $radius = $value / ( 2 * Pi ); } elsif ($prop eq 'diameter') { $radius = $value / 2; } else { die "Property '$prop' cannot be defined"; } $self->[ATR_RADIUS] = $radius; } sub learn { my $self = shift; my $prop = shift; my $radius = $self->[ATR_RADIUS]; my $value = 0; if ($prop eq 'radius') { $value = $radius; } elsif ($prop eq 'area') { $value = Pi * ($radius ** 2); } elsif ($prop eq 'circumference' or $prop eq 'circum') { $value = 2 * Pi * $radius; } elsif ($prop eq 'diameter') { $value = $radius * 2; } else { die "'$prop' is not a property of a Circle"; } return $value; } sub grow { my $self = shift; my ($prop, $value) = @_; my $old_value = $self->learn($prop); $value += $old_value; $self->redefine($prop => $value); } sub shrink { my $self = shift; $self->grow(shift, 0-shift); # shrink is neg. growth } 1;
    <-radiant.matrix->
    A collection of thoughts and links from the minds of geeks
    The Code that can be seen is not the true Code
    "In any sufficiently large group of people, most are idiots" - Kaa's Law
      Using a cascaded if/elsif/else instead of individual methods looks terribly awkward to me. I think that's part of what OO and methods (or even subroutines in general) are supposed to help us avoid!

        Yes, the implementation is poor, but it is meant to be a simple demonstration. I tend to choose dispatch-table type patterns for such things, and if the normalization/etc. were more complex I might even have "private" subroutines to deal with the attributes.

        My post is more targetted toward interface than internal implementation; still, your point is a good one.

        <-radiant.matrix->
        A collection of thoughts and links from the minds of geeks
        The Code that can be seen is not the true Code
        "In any sufficiently large group of people, most are idiots" - Kaa's Law

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others avoiding work at the Monastery: (6)
As of 2024-03-29 12:49 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found