Terminology issue: IIRC (and please correct me if I'm wrong), Perl 6
uses 'module' to refer to 'a perl 5-or-earlier module', and uses
'package' to refer to the perl 6-or-later equivalent.

Aaron Sherman wrote:
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.

OK.  My understanding of "programming by contract" as a paradigm is
that one programmer figures out what tools he's going to need for the
application that he's working on, and then farms out the actual
creation of those tools to another programmer.

Second, when you mention 'signature criteria', what immediately comes
to mind is the notion of the signature, which applies restrictions on
the various parts of an argument list:

   :(@array, *%adverbs)

This applies two restrictions: there can be only one positional
parameter, and it must do the things that a list can do.  Change the
comma to a colon, and you have a signature that says that there must
be a list-like invocant, and that there can be no positional
parameters.

The only aspect of the signature that is not concerned with argument
types is the part that determines how many of a particular kind of
parameter (invocant, positional, or named) you are required or
permitted to have: even the @-sigil in the first positional parameter
(or the invocant, in the method-based signature) is specifying type
information, as it's placing the requirement that that parameter needs
to behave like a list.

In effect, I could see thinking of a signature as being a regex-like
entity, but specialized for matching against parameter lists (i.e.,
capture objects) instead of strings.

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.

This would seem to be a case for changing the above to something along
the lines of:

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

This removes all references to Array from the signature, and leaves it
up to the @-sigil to identify that the invocant is supposed to be some
sort of list-like entity.  The change above from 'method' to
'submethod' is predicated on the idea that methods have to be defined
within a class or role, much like attributes have to be; if this is
incorrect, then it could be left as 'method'.

I'm thinking of
something like:

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

The Synposes already define a 'proto' keyword for use with routines;
it's listed right alongside 'multi'.  Were you intending to refer to
this existing keyword, or did you have something else in mind?

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:

In short, you want to define a signature that every routine with the
given name must conform to, whether that routine is a sub or submethod
defined in the package directly, or if it is a method defined in a
class or role that is in turn defined in the package.

While 'role Foo { our method max(@array:) { ... } }' specifies that
whatever composes the role in question must include a method called
max that takes a list-like object as an invocant, you want to be able
to say that any method, sub, submethod, or other routine defined in a
given package that is called 'max' must match the signature ':(@array,
*%adverbs)'.  This would seem to bear some resemblance to Perl 6's
notion of 'subtypes', which add matching criteria to objects, and
throw exceptions whenever you try to assign a value to the object that
doesn't meet the criteria.

The goal, here, is to allow us to centrally assert that "Perl provides
this subroutine" without defining its types or behavior just yet.

Here's the thing: the above doesn't seem to require that any such
subroutine be defined.  That is, the coder could forego defining _any_
'max' routines when he implements your documentation, and the first
indication you'd have of this oversight would be when your application
complains that the function doesn't exist.  That is, you're not saying
'this module provides this subroutine'; you're saying 'if this module
provides this subroutine, it will look something like this.'  I'm not
sure how useful that would be.  Composing a role, OTOH, says 'this
package provides this routine' without (neccessarily) defining its
types or behaviors just yet.

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.

Personally, I don't like the idea of embedding code in documentation.

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.

More generally, this would be a mechanism which lets you apply traits
to every routine that matches the defined pattern:

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

would apply the 'export' trait to every routine named 'max' that's
visible in the current 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.

I think that a more useful tool might be a more general assertion
mechanism which includes signature-matching as one of its options.  I
could see benefit to being able to write a package which is a mixture
of documentation (telling the programmer what you want) and assertions
(telling him when his code isn't what you want).  The trick is to keep
the assertion syntax simple enough that you don't do more coding when
writing the assertions than the programmer does when writing the
actual code.  This is a very real danger, as conceptually this notion
of assertions is akin to the concept of schema for XML.

--
Jonathan "Dataweaver" Lang

Reply via email to