Larry,
On Oct 11, 2005, at 8:47 PM, Larry Wall wrote:
On Tue, Oct 11, 2005 at 06:10:41PM -0400, Stevan Little wrote:
: Hello all.
:
: I would like to propose that class methods do not get inherited
along
: normal class lines.
I think most class methods should be written as submethods instead.
In which case they would not be inherited then. I (obviously) agree
with you on that :)
You seem to be arguing that a class has no state, but my view is that,
in the abstract, a class encompasses the state of *all* its objects.
It just hasn't picked one particular object to be at the moment.
No, not that class has no state, but that with the currently specced
classes we have inherited behaviors (class methods) but they do not
inherit the accompanying state (class attributes) as well. I see this
as potentially very problematic.
: == Instance Counting Class
:
: The most common example given for class methods is an "instance
: counter". Here is how one might (naively) look in Perl 6:
:
: class A {
: our $.count;
: method count (Class $c:) { $.count; }
: submethod BUILD {
: $.count++;
: }
: }
That's obviously broken--the count accessor should be a submethod
to be
consistent, unless the explicit intent is that any subclass of A
return the count of A's. Which, not surprisingly, is exactly what
you get below. It should probably have been declared:
our $.A_count;
in that case. And in which case you don't need the explicit accessor,
since one would have been provided because of the dot. If you don't
want the autoaccessor, don't use the dot.
Yes, this example was purposefully broken, but also copied from
several tutorial on "how to use class methods".
I suppose an argument could be made that autoaccessors for class vars
should be submethods by default. Or maybe "my $.count" makes a
submethod,
while "our $.A_count" makes a method.
I think that is probably not a bad idea. If we not going to inherit
the class state, then we should not inherit the class method either.
: Clearly, we only have one instance of A, and one instance of B, so
: those numbers are wrong. It could be argued that since B is a
subtype
: of A, we do have two A's, but the argument does not work in
reverse.
Sure it does. It doesn't matter whether B is a subtype of A or not,
you've given it an interface to code that counts A's.
True, that was probably a bad example (culled from other bad examples).
: But either way, I would argue that the results shown above are
: misleading, and probably not what the programmer intended.
That's certainly possible, but it wouldn't be the first time people
have been surprised that the computer did exactly what they asked it
to... :-)
Also very true.
: Sure, you could do that, however, it complicates the meta-model
: unnecessarily. It is much easier to accomplish this using a
subclass
: of Class.
:
: class CountingClass is Class {
: has $.counter;
: method count (CountingClass $c:) { $.counter; }
: method new (CountingClass $c: %params) {
: $.counter++;
: next;
: }
: }
:
: class A meta CountingClass {}
: class B meta CountingClass {}
:
: Now A and B both have their own counters neither of which interfere
: with one another.
Only by forcing people to repeat themselves as a matter of policy.
And policy could just as easily have added "our $.count" to B. The
real trick would be to set up A such that you don't have to do
anything
special in B. I suppose we could say that, by default, if A isa B,
then A gets also gets whatever metaclass B has, not the standard
metaclass supplied by "class".
I considered this as well, it seems to make some sense that the
metaclass of A is also the metaclass of B if A isa B. This would
actually simplify a particular edge case with the eigenclasses that
was problematic.
This diagram assumes we have inheritable class methods, and they are
implemented using the eigenclasses.
Class
^
:
eFoo<.......eBar
^ ^
| |
Foo<.......Bar
The method dispatch for Bar will go first to it's class (eBar), then
to any superclasses (eFoo), and any of their superclasses (Class),
and so on, and so forth. When you introduce a custom metaclass like so:
Class
^
:
CustomMeta
^
:
eFoo<.......eBar
^ ^
| |
Foo<.......Bar
A "problem" occurs with method dispatch for Bar. First it will go to
eBar, then to eFoo, then to CustomMeta, then to Class, etc, etc.
Since Bar was not explicitly created with CustomMeta, this is not
correct.
However, if the metaclasses act as you describe, this is then does
exactly what it is supposed to. I think it is a sane approach
personally.
But these are metaclasses, not classes. You keep writing the type of
the invocant of class methods as Class, but I don't believe that
anymore.
Sorry, just following the A12 examples :)
The type of the invocant of a class method in A is A, not Class. It
just happens to be a generic A, not a specific A, and tests as
undefined.
But the "type" of A is a Class correct?
If so, then this becomes a special case in the parameter parsing
which can take an instance rather than just the "type-of" an instance.
If we follow this more, then do we end up with things like Prolog/ML/
Erlang style pattern matching?
multi length([]) { 0 }
multi length(List @l) { 1 + length(@l.tail) }
Where params can be either (type => name) pairs, or some kind of
prototypical instance which we try to match against.
So then the class methods would actually look like this maybe:
method my_class_method (::A) { ... }
: Of course the "meta" syntax there is speculative,
: but surely you can accomplish that behavior somehow. This approach
: actually uses no class methods, only instance methods.
You can already do this sort of thing using the trait syntax.
That's why I go on about "traitorous traits" in A12: "is foo" can
do *anything* to the container on its left, including installing
new parent classes and metaclasses. (But if I did want to define an
important new default-overriding metaclass, I'd probably replace the
"class" keyword with a word like "role", or "union", or "theory",
or whatever I thought was appropriate. Not sure counters rise to
that level though.)
I thing that would maybe be overkill for specificing custom
metaclasses. Wouldn't it suffer from the same "repeating yourself"
policy issues you described above?
: NOTE: I am assuming that the Countable role will mix-in the class
: method &count as well as the class attribute $.count. And that
"A but
: Countable" is really sugar for something like "class { does
: Countable; is A }".
Well, hey, we're out ahead of the state of the art here, so we can
do whatever we like with roles.
At this point, I assume that anything and everything inside a role is
consumed by the class, so that the role can be then be disposable
(unless of course another class wants to use it).
: == Custom Constructors
:
: Another common example of class method usage is custom
constructors.
: This example is moot given the BUILDALL/BUILD system. All class
: specific initialization can easily be done using custom BUILD
: submethods.
The purpose of custom constructors in Perl 6 is to allow the use of
positional args. I can't think of any other use for them, offhand...
That's all I got too. And I am not sure you would always want to that
be inherited either.
: In Smalltalk, "new" is
: actually an instance method of the class Class, and it calls the
: specific object's "new" instance method to initialize (somewhat
like
: the CREATE->BUILDALL/BUILD in Perl 6). In the Perl6-MetaModel
: prototype, &new is implemented as an instance method of Class, and
: not a class method of Object (as is sometimes assumed).
I think that, at least as a name, the default new() *should* be in
Object, though of course most of its work might done by Object.meta
instead.
So &new in Object a class method? Why? It works just fine as an
instance method of Class.
I don't believe in Class as a class anymore, unless that's
what .meta reaches these days.
What would Class be if it is not itself an instance of Class?
If you want to change the behavior
of a class, you're really talking about changing the behavior of
the metaclass instance (which typically stores various bits of its
data in a package). The A class is just a proxy for all that, so
that you can reason about objects of type A without actually having
one.
Having an instance of Class which describes A allows you to reason
about A very easily without needing an instance of A. I think maybe
the ways in which you reason about it might be different, but there
is no reason you cannot do so. A Class instance should respond
to .isa and .does exactly the same way an instance of said class
would. The rest can be done with .meta introspection on superclasses,
methods and attributes. Classes are just more complex beasts than
instances, I think a reflection API/Meta Object Protocol will give
you all that you need and more.
I'm beginning to suspect Class is just a mixin to that breaks the
instance association, such that if you did
$fido = new Dog;
$fido does Class;
you'd end up with $fido undefined (but still of type Dog).
I am not sure that code makes sense. How would that be useful?
Usefulness aside, why do Roles and Classes need to be seperate
beasts? In the current meta-model prototype, the role system is laid
atop the class system so that the following is true:
Class is an instance of Class
Role is an instance of Class
Class does Role
This then means that Role also .does Role since Role is an instance
of Class (which does Role).
It gets very cyclical, but it essentially means that all classes can
be treated as roles. This allows for all sorts of interesting things
to happen actually.
I have to admit though, that this comes directly from Scala (so maybe
we are not alone here out on the edge :)
So basically, you declare your constructor
method new (Class $c: ...)
if you want to restrict $c to something that does the Class
*role*, which
guarantees there's no associated instance.
I think this could be accomplished by just saying "parameter checking
checks .does() and not .isa()" (which would make chromatic happy).
But I am not sure there is a need to make Class a role.
But if you're more into prototyping, you'd say
method modified (Dog $d: ...)
if you want to restrict $d to something that does Dog, and you don't
care whether it happens to have an instance associated because the
class already defines decent defaults for any attributes you don't
override.
I think this would work as well if parameters checked .does() since
the Dog class surely .does(Dog).
I want Perl 6 to encompass both class-based and prototype-based
approaches, and this seems like the right way to do it.
I think given enough meta-accessibility, a model whose core is class-
based can be easily used as prototype-based.
I think there's no such thing as a Class instance. That simplifies
things even further.
Do you mean no instance of Class called "Class"? or no instances of
Class at all? If so, how would we represent classes?
Stevan