Here's how I see roles.  This is just an attempt to formalize our
concepts so that the answer becomes an obvious truth rather than a
decision.

A role is an interface with some default implementations.  Here's an example:

    role Foo {
        # anything that conforms to Foo must provide a foo() method
        # if one is not provided, this body is used instead
        method foo () { say "hi" }

        # anything that conforms to Foo must provide a bar() method
        # if one is not provided, then there is an error
        method bar () {...}

        # anything that conforms to Foo must provide a baz() method
        # if one is not provided, then an instance variable is used instead
        has $.baz;
    }

    role Bar {
        # anything that conforms to Bar must provide a bar() method
        method bar() {...}
    }

    # this is a conditional: anything that conforms to FooBar also
    # conforms to Foo and Bar
    role FooBar {
        does Foo;
        does Bar;

        # the requirement for the implementation of a bar() method is
        # carried over[1]
    }

This is a sort of suspension.  If you ignore the roles Foo and Bar,
you have simply a role FooBar that has three requirements: foo(),
bar(), and baz(), where foo() and baz() have defaults.  When you
compose this into an empty class, you will get not a conflict but a
missing method error.  bar() was not implemented.

Now, let's say that bar() was given a default implementation in Foo. 
Then FooBar would be a fully-defined role with no requirements.

Let's say that bar() was given a default implementation in both Foo
and Bar.  That's a conflict.  But let's follow suit and pretend that
Foo and Bar didn't exist.  If you try to compose in FooBar, you will
get an ambiguity, which can be resolved by defining your own bar(). 
So clearly, FooBar simply has a required foo() again.

To summarize, as we'll use this later:

    Foo:    foo() default, bar() default, baz() default
    Bar:    bar() default
    FooBar: bar() required

So then we take that, and say that we can give a default
implementation for this now-required method.  FooBar may disambiguate.

Moving on.

    role Baz {
        does Bar;
    }

By my free-derivation (or composition in this case, I guess)
principle, Baz is now equivalent to Foo.  If you think of them as
interfaces, it makes perfect sense.  Baz provides no additional
implementations, nor imposes any additional requirements, and thus you
must do precisely the same things to conform to Baz as to Foo.

    Baz: bar() default

Now:

    class MyClass does FooBar does Baz { }

Here's where the theory proposal comes in, yet again.  The definition
of a class is just the definition of a role together with a
(syntactically impossible) declaration of a concrete type.   Creating
the class is erroneous if the role is not fully-defined.  So let's
just look at the role half:

    role MyClass {
        does FooBar;
        does Baz;
    }

Now we ignore the inner workings of FooBar and Baz.  This is a good
idea, as it lets us refactor freely, as long as it looks the same from
the outside.  Recall that we had:

    Baz:     bar() default
    FooBar:  bar() required

So clearly Baz fulfills FooBar's requirements, and MyClass is a
fully-defined role.

Okay, how did that happen?  What was the formality that we actually used?

It was the fact that at each stage of the game, we summarized the
defaults and requirements for each role, ignoring the internal makeup
(i.e., what roles were composed into it, etc.).

And it sounds correct to me.

Luke

[1] Perhaps we even require declaration.  We probably shouldn't, in
the name of keeping Perl Perl.  Any good documentation system ought to
catalog all the dependencies.

Reply via email to