Executive summary:

I suggest a signature prototype that all multis defined in or exported to the current namespace must match (they match if the proto would allow the same argument list as the multi, though the multi may be more specific). Prototypes are exportable. Documentation tie-ins are also suggested, ultimately allowing for documentation-only interface modules which collect and re-export the interfaces of implementation modules while providing high-level documentation and constraints.

Details:

Larry has said that programming by contract is one of the many paradigms that he'd like Perl 6 to handle. To that end, I'd like to suggest a way to assert that "there will be multi subs defined that match the following signature criteria" in order to better manage and document the assumptions of the language now that methods can export themselves as multi wrappers. Let me explain why.

In the continuing evolution of the API documents and S29, we are moving away from documentation like:

        our Scalar multi max(Array @list) {...}
        our Scalar multi method Array::max(Array @array:) {...}

toward exported methods:

        our Scalar multi method Array::max(Array @array:)
                is export {...}

"is export" forces this to be exported as a function that operates on
its invocant, wrapping the method call. OK, that's fine, but Array isn't
the only place that will happen, and the various exported max functions
should probably have some unifying interface declared. I'm thinking of
something like:

        our proto max(@array, *%adverbs) {...}

This suggests that any "max" subroutine defined as multi in--or exported to--this scope that does not conform to this prototype is invalid. Perl will throw an error at compile-time if it sees this subsequently:

        our Any multi method Array::max(Array @array: $x)
                is export {...}

However, this would be fine:

        our Any multi method Array::max(Array @array: :$x)
                is export {...}

because the prototype allows for any number of named parameters.

The default behavior would be to assume a prototype of:

        our proto max([EMAIL PROTECTED], *%namedargs) {...}

Which allows for any signature.

Any types used will constrain multis to explicitly matching those types or compatible types, so:

        our Int proto max(Seq @seq, *%adverbs) {...}

Would not allow for a max multi that returned a string (probably not a good idea).

The goal, here, is to allow us to centrally assert that "Perl provides this subroutine" without defining its types or behavior just yet. Documentation/code could be written for the prototype:

        =item max
        
        =inline our proto max(@array, *%adverbs) is export {...}
        
        C<max> takes an input sequence or array (C<@array>) and
        returns the maximum value from the sequence.
        Specific implementations of max may be defined which
        allow comparators or other adverbs (C<%adverbs>) to
        be defined.
        
        =cut

I've invented the "=inline" POD keyword here as an arm-wave to programming by contract (both Perl and POD read its argument). If it's not liked, the proto could be duplicated both inside and outside of the documentation as we do now. Kwid, when it comes to pass, could provide similar mechanisms. Given this, an entire "interface-only" module could exist as POD/Kwid-only, which isn't a bad thing given that pre-processed bytecode will be what most people are loading anyway, and thus not parsing the POD every time as in Perl 5.

There's also another interesting thing that we might or might not decide to tack onto protos, which is that the "is export" tag on one could cause the exporter mechanism to automatically export any "is export" tagged subroutines from the current namespace that match this prototype, even if they came from a different namespace. Essentially defining one proto allows you to re-export any multis that you imported by that name. This seems to me to be a better mechanism than a simple :REEXPORT tag or the like on the "use", as it more explicitly targets the interfaces that your module defines its own prototype for.

This produces a generic set of documentation for a module that might only act as a re-exporter for other modules. e.g. the above might appear in a module called CORE which is "use"d by the runtime automatically, and uses various other modules like Math::Basic and List without any explicit export tags, thus providing the minimal interfaces that Perl promises. S29 could eventually be adapted as the documentation for the prototypes in that module without having to actually document the individual APIs of the rest of the Perl runtime.

In Perl 6, therefore, "perldoc perlfunc" would become "perldoc CORE" or whatever we call that module.

This is only a first step to programming by contract, which has many more elements than simply blending signatures into documentation (assertions and other elements are also part of it), but I consider it an important step in the process to becoming more PbC-aware.

Any thoughts?

Reply via email to