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. : I think that inheriting class methods will, in many cases, not DWIM. : This is largely because your are inheriting behavior, and not state : (since class attributes are not inheritable). Let me explain in more : detail. : : Let's start by making a very basic definition of an *object*, : ignoring any implementation details or specifics. : : object == state + behavior : : This statement assumes that *objects* at their core are a unique : state coupled with a collection of behaviors to act upon that : particular state. Of course we are ignoring all the other class/meta/ : inheritence junk for now. : : To take away the behavior, and only be left with state would degrade : our object to the level of C struct or Pascal-style record-type. To : take away the state, and only be left with behavior, would basically : leave a module/package or some pseudo-random collection of functions. : : So at this point, I think it is safe to say that an *object* should : have both state and behavior. 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. : Now, back down from the theoretical cloud to reality. I would like to : show some canonical class-method examples (and in some cases, show : how they are broken), then show how they might be better accomplished : in Perl 6 without the need for class methods to be inherited. : : == 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. 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. : Each time an instance of A is created the counter is incremented. So : that ... : : A.count; # 0 : A.new; : A.count; # 1 : : Now this makes sense, until we subclass A. : : class B is A {} : : A.count; # still 1 : B.new; # calls A::BUILD : : A.count; # 2 : B.count; # 2 : : 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. : 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... :-) : What is happening here is that we are inheriting behavior, but not : inheriting state. Which goes against the core definition of *objects*. I'd say that state is precisely what we're inheriting here. It's just the state of something that is not the object. But that was already true of class A as well. : "I can solve this, just make class attributes inheritable?", you say. Don't see how that would help, unless you also force every class to redeclare the attributes. (By the way, attributes of the form $.count are already inheritable, being shorthand for $?SELF.count.) : 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". 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. 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. : 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.) : However, as always, there is more than one way to do it, you can : accomplish the same thing using Roles. Here is how that might look: : : role Countable { : our $.count; : method count (Class $c:) { $.count; } : submethod BUILD { : $.count++; : } : } : : CountingA = A but Countable; : CountingB = B but Countable; Again, you're forcing the redundancy on the user. : CountingA.count; # 0 : CountingA.new; : CountingA.count; # 1 : : CountingB.count; # 0 : CountingB.new; : CountingB.count; # 1 : : 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. : == 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... : This example too is skewed towards a language's particular object : model as well. In Java/C# the constructor is a special/magical : "thing" which is called by the "new" keyword. Closer to Perl's .bless builtin, really. : 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. I don't believe in Class as a class anymore, unless that's what .meta reaches these days. 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. 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). 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. 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 want Perl 6 to encompass both class-based and prototype-based approaches, and this seems like the right way to do it. : == Java-style static methods : : Java's static methods are only thought of as being like class methods : because they have access to other static class members, and they are : only callable "though" the class. The Perl 6 equivalent of this : concept is nothing more than a package sub, and a package variable. : Since Class isa Package, this type of behavior is easily accomplished. I think private methods can fill this role in Perl 6, and already have the privacy/trust thing built-in. : == Conclusion : : Now, I am not proposing we abolish class methods entirely, only that : we simplify them. If we do not require that class methods be : inherited, then they can be implemented as ruby-style "singleton- : methods" on the Class instance. This keeps the internals of the meta- : model clean and orderly (always a good thing IMHO :). : : Anyway, I have said my peace, what do you all think? I think there's no such thing as a Class instance. That simplifies things even further. Larry