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