in reply to Idea on a Base class API

This definitely seems like a worthwhile WTDI. However, I think I'd rather see the following syntax instead of your define_attributes:

package Bar; use BaseClass { name => '$', values => '@', anarrayref => '*@', };

Copy from base the ability to automatically add BaseClass.pm to @Bar::ISA. Copy from Class::Struct the syntax of specifying elements. (Although, personally I'd prefer a slash instead of an asterisk to represent refs and the ability to use '[ ]' and '{}' -- it's more Perlish that way.)

This way, you've combined the BaseClass, @ISA, and define_attributes statements into one handy statement that's terse but clear.

Then, to handle inheritance, you could use something like this:

use BaseClass qw(Foo Fub) { name => '$', };
And now the object would have the attributes from Foo and Fub, as well as the ones specified in the anonymous hash.

-- Frag.

Replies are listed 'Best First'.
Initial draft of a BaseClass contract (Updated)
by dragonchild (Archbishop) on Jun 13, 2001 at 19:11 UTC
    That's a very different approach to how I was thinking about it. You're saying that BaseClass should control all of the inheritance, vs. each class doing the inheritance.

    I'm not looking for technical solutions right now, though. I appreciate them, and I know that's how most people (including me) tend to think, which is why I want to shy away from them for now. I want to create a contract. Something that determines how someone will use BaseClass, regardless of how it's implemented. (I already have two implementations for objects, and I know there have to be more than that!) Maybe, something like this:

    BaseClass will do the following:

    1. Support a define_attributes() function. This will have the following attributes:
      • It will set the attributes that exist in the current class.
      • It will look in that class's @ISA to find all parent classes (if any) and include those classes's attributes.
      • It will be call-able from the package level, in the form Foo->define_attributes(). This will define attributes for the class.
      • It will call-able from the object level, in the form $self->define_attributes(). These will define attributes for the class.
      • define_attributes() will take a hash of hashes, in the form { attribute => { Type => $val1, Default => $val2 }.
      • $val1 will be a listref containing which types are legal for this attribute.
        • NUMBER will require that the value have only digits within it. ref will return undef.
        • SCALAR will allow any alphanumeric scalar. No references. ref will return undef.
        • HASHREF will allow any reference to a hash as determined by ref.
        • LISTREF will allow any reference to a list as determined by ref.
        • SCALARREF will allow any reference to a scalar, as determined by ref.
        • REFERENCE will allow any reference, as determined by ref.
        • OBJECT will allow any reference to a class, as determined by ref.
        • Foo::Bar will allow any reference to the class Foo::Bar, as determined by ref.
        • Foo::* will allow any reference to a class matching that string, as determined by ref.
      • $val2 will be a value of a legal type for this attribute, as checked by $val1, and will be the value copied into the attribute whenever a new object is instantiated.
    2. Support a new() constructor.
      • new() will be call-able from the package level, in the form Foo->new($val1).
      • new() will return a bless'ed reference to an object of class Foo.
      • $val1, if it exists, will be a hash (or hashref), where the keys are pre-defined attribute names and the values are the values for those keys.
      • new() will call a function called initialize() as its last action prior to returning the blessed reference, passing it $val1.
    3. Support an initialize function. This is meant to be overloaded, should there be class-specific initializing actions. Naming attributes and setting default values should be done using define_attributes().
      • The BaseClass initialize() function will solely set the values of all attributes passed into the call to new().
      • Every overload of initialize() should call $self->SUPER::initialize(@_), to make sure that the parent class(es) do their initial setup work, if any.
    4. Support a clone() function. This will be called in the form $self->clone.
      • clone() will return a blessed reference to the class of $self.
      • All values in the original will be copied to the given attribute in the copy.
      • If a value is a reference, then the values referred to will be copied, not the reference. This rule will be recursive, so as to deal with lists of hashes of objrefs, etc.
    5. Support a number of functions that will act as accessors and/or maintenance functions. All of these functions will take a list(ref) or hash(ref), as necessary, and return a scalar or list, depending on calling context. These functions will be called in the form $self->func($val1);
      • exists() will take a list(ref) of attribute names and return the type-list of an attribute, if it exists within the class, or undef.
      • get() will take a list(ref) of attribute names and return the value stored in that attribute, if it exists within the class, or undef.
      • set() will take a hash(ref) of attribute names and values. It will validate that the given value matches the allowable type(s) for that attribute and, if it does, set the attribute to the new value. It will return 1 if that attribute exists and the value was allowable and 0 otherwise.

    Update:Added clone() after reading Damian Conway's book. (Forgot about copying!)

      I'm going to have to read this more carefully later, but I just want to clarify what I was suggesting.
      • First, use BaseClass qw(Foo) would be only one way to handle inheritance. BaseClass would use all of the items in @ISA at that point to determine attributes (as you suggest) but it would also be smart enough to look at that "Foo" in its @_. In other words, your mechanism for dealing with inheritance would still be there; my suggestion would simply provide another way to do it, one that's imho a lot simpler. But my approach wouldn't be mandatory, but optional.
      • Similarly, since any given module is going to have only one call to define_attributes (is that correct?), it makes sense to me that it be part of the use call. If an anon. hash is not on the use BaseClass line, BaseClass would defer setting up attributes, leaving it to a define_atttributes call later in the module. There'll be more than one way to do it.

      I think I'm not questioning the underlying ideas of your approach, but rather, I'm suggesting a simplified user interface towards invoking it. Or if not simplified, then in keeping with currently existing Perl modules that already get a lot of use and that imho have good interfaces.

      Oh, and just I gotta say: I really hate the idea of having to type "SCALAR" et al. Perl already has the sigil set - why duplicate them? (Except in a 'use English' way, but again, that should be an optional WTDI.)

      -- Frag.