In a message dated Sun, 1 Oct 2006, Aaron Sherman writes:
Trey Harris wrote:
In a message dated Fri, 29 Sep 2006, Aaron Sherman writes:
[snip]
However, that's not to say that a class can't be abstract, just that a class that does an interface (a role with nothing but abstract methods) must implement the role's interface.

So why would it generate an error? Why wouldn't it merely result in B being abstract too, assuming that contra my prior mail, classes can be abstract?

What use is an interface if it doesn't give you a guarantee? If I say, "all dogs can bark," and you define a dog that can't bark, that's not "abstract", that's a failure to meet the interface requirements of a dog.

No--I wouldn't define a dog that can't bark, I'd define a dog that *can* bark, but I wouldn't say *how* it could bark (conjecturing an "is abstract" class trait I give a sample implementation of below):

  class Dog is abstract { method bark () { ... } #[ ... ]  }
  class Pug is Dog { method bark () { self.vocalize($.barkNoise) } }

  my Dog $fido .= new; # exception, instantiating abstract class
  my Dog $fido  = Pug.new; # good, $fido can bark

Seems like there would be three ways to achieve the guarantee: an explicit "is abstract" marker like above to prevent instantiation; some way to infer concreteness (this gets into my earlier question of whether yada-yada is enough to do so) so you can prevent instantiation; or simply disallow abstract classes entirely.

It sounds like the assumption thus far has been that the existance of roles imply that abstract classes are disallowed, so you'd write:

  role Dog { method bark { ... } #[ ... ] }
  class Pug does Dog { method bark { .vocalize($.barkNoise) } }

S12 says: "Classes are primarily for instance management, not code reuse.
Consider using C<roles> when you simply want to factor out
common code."

But if I want to write code that handles the instance management for several related classes but cannot be instantiated itself, that must be a role and not a class. But instance management, as the S12 quote says, is the purpose of classes, not roles. Is it a deep requirement of the MOP/MRP that only one of classes and roles can be abstract?

I've looked at my uses of uninstantiable classes in Perl 5 for the past few years, and I think that almost all of them could be done as roles (though for some of them the cognitive dissonance of calling a set of object-management methods a "role" still bothers me). But there's one case where I can't figure out how to do it except for throwing a runtime exception in a class.

For a system monitoring application, I have a class heirarchy like the following (bare names indicate concrete instantiable classes, brackets indicate uninstantiable abstract ones):

+ [SystemMonitor]
  -   CPUMonitor
  -   DiskMonitor
  +   ScriptedMonitor
      +   [HardwareMonitor]
          -   FanMonitor
          -   TempMonitor
          -   PowerSupplyMonitor

Here, SystemMonitor is abstract and sets up the data collection and storage routines. Its concrete subclasses implement how to actually get the data and any munging the data requires, but otherwise inherit their behavior from SystemMonitor. ScriptedMonitor is a concrete class that gets a script attribute which it runs and a closure attribute it uses to munge the data the script generates.

Turns out that there are many HardwareMonitors that all run the same suite of hardware monitoring scripts and performs the same munging on them, but has almost the same behavior as ScriptedMonitor. So I handled that by subclassing it with a new abstract class, HardwareMonitor, which factored out the new behavior all the hardware monitors shared. I then subclassed *that* with concrete classes implementing the last little unfactorable bits. So Abstract <- Concrete <- Abstract <- Concrete.

new(), for instance, was defined only in SystemMonitor (but threw an exception if you tried to call it on SystemMonitor, thus making the class abstract); gatherData() is called in SystemMonitor but is defined only in the direct subclasses of SystemMonitor, and is overridden in HardwareMonitor with a call to the superclass method (ScriptedMonitor::gatherData). HardwareMonitor's subclasses just define some munging methods that HardwareMonitor's processData() methods calls.

In this way, I never repeat myself, I use polymorphism so that I never write any conditionals involving the type of anything (except in the case of new() throwing an exception to prevent instantiation of an abstract class), and related code goes together. This is a good thing.

In Perl 6, the abstract SystemMonitor could be a role, and a concrete ScriptedMonitor could be a class that does SystemMonitor, but it's not at all clear to me what HardwareMonitor would be, since classes can't be abstract and roles can't inherit from classes. I guess it would be a role, but then we'd have something like:

- role SystemMonitor
- class CPUMonitor does SystemMonitor
- class DiskMonitor does SystemMonitor
- class ScriptedMonitor does SystemMonitor
- role HardwareMonitor does SystemMonitor
- class FanMonitor does HardwareMonitor
- class TempMonitor does HardwareMonitor
- class PowerSupplyMonitor does HardwareMonitor

and I'd have to repeat the non-overridden parts of ScriptedMonitor in HardwareMonitor.

I don't see where this is a win for me--it looks very much like a loss, as my clear class heirarchy with no repetition has now become a flat set of classes with tightly-coupled mixins requiring a repetition.

I could factor out the repetition with a third role that both the ScriptedMonitor class and the HardwareMonitor role both do, but now I have one more package than I had in the Perl 5 program, and I'm not sure why. What have I gained by having it?

And the fact that I can't easily figure out a name that this role would have apart from ScriptedMonitor also alarms me--I generally consider an inability to see an obvious name for a software component to be a good sign that my software composition is faulty.

So let me just ask the question bluntly: why can't we have classes that can't be instantiated (short of checking .WHAT and explicitly throwing a runtime exception)? Heck, to hack it in as a runtime exception, all we need is something like:

  role abstract {
      has @!classes;
      multi sub trait_auxiliary:is(abstract $trait, Class $container:) {
          # $container might be concrete class, so we use ::?CLASS
          # to get at the lexical class 'is abstract' was written on;
          # we keep a list because there might be multiple abstract
          # classes in our instance's composition
          @!classes.push(::?CLASS);
      }
      submethod BUILD {
          if (self.WHAT == any(@!classes)) {
              croak "Attempt to instantiate abstract class {self.WHAT}";
          }
      }
  }

Trey

Reply via email to