We've made great leaps in phaser technology from S04 in Rakudo in the past week. We used to have C<BEGIN>, C<CHECK>, C<INIT>, and C<END>. Now we also have C<ENTER>, C<LEAVE>, C<KEEP>, C<UNDO>, and (for C<for> loops) C<FIRST>, C<NEXT>, and C<LAST>. In his latest blog post [1] jnthn++ says he'll move on to C<PRE> and C<POST> but that he'll "need some spec clarifications first".
This mail is meant to kick off the requisite spec clarifications. I really like what these features could be if the spec made any sense when talking about them, which it currently doesn't. C<PRE> and C<POST> for ordinary blocks are fairly straightforward already. C<PRE> and C<POST> for methods are the problematic ones. There are two problems with them now as the spec is written. 1. The method forms of C<PRE> and C<POST> don't follow sane lexical scoping rules. 2. The whole DBC bit rests on a foundation which simply isn't there in vanilla Perl 6. I will say a bit about each of these in turn. == The method forms of C<PRE> and C<POST> don't follow sane lexical scoping rules In Synopsis 4 [2], a C<CALL-VIA-DBC> method is postulated, which runs outside the actual method called. Diverting a method call is certainly possible with the MOP we have today, but it comes off as too invasive and bulky a solution to apply to all method calls, ever. Also, it feels upside-down to have a method (a user-definable construct) handle phasers (which are built into the language). The C<CALL-VIA-DBC> method is meant to do eight things from a list, detailed in S04. Those eight things appear reasonable, but contain a logical impossibility. Let's say we're calling method C<.foo> in some class, and that method has a C<PRE> block. You can't do number 3 on the list (calling the C<PRE> phaser in C<.foo>) without first calling C<.foo>. But calling C<.foo> shouldn't happen until number 5 on the list! (Why do we need to call C<.foo> in order to do number 3 on the list? Simply because the C<PRE> block needs to be initialized correctly so that it sees method parameters correctly, and that doesn't happen until after the usual method call and parameter binding is made.) Of course, it could be argued that this impossibility is resolved by postulating very special scoping rules, or calling conventions, for methods involving C<PRE> and C<POST> blocks. I believe that's a bad idea. If we're to have a solution, it should be one that builds on the usual scoping rules and routine calling conventions. This is how other good Perl 6 features have fallen out in the end. The proposed DBC solution in S04 clearly doesn't build on the usual scoping and calling, and needs to be changed or removed. == The whole DBC bit rests on a foundation which simply isn't there in vanilla Perl 6 The same bit of S04 makes it seem like more than method has C<PRE> and C<POST> blocks that should be run and validated for any given method. (Number 3 on that list says "call all the appropriate per-method C<PRE> phasers", and there's the notion of a "postcall stack".) One might assume that "the appropriate per-method C<PRE> phasers" include the C<PRE> phasers of all corresponding methods up the inheritance chain. After all, this is what DBC is about: having methods in deriving classes loosen their preconditions and tighten their postconditions. So it makes sense to involve base classes' methods. Except it doesn't, not in a language like Perl 6 without *any* requirement of even basic signature compatibility between base and deriving classes. class A { method foo($x) { PRE { $x < 10 } # ... } } class B is A { method foo($a, $b, $c) { PRE { [>] $a, $b, $c } # ... } } When C<B.foo> is called, are both C<PRE> blocks meant to be run? What's the value of C<$x>? In which lexical scope do we look up C<$x>? (Clue: there isn't any.) Actually, while this example highlights the absurdity of attempting DBC on a system which doesn't enforce signature compatibility -- and it might be possible to switch that on with a C<use Liskov;> pragma or something -- the basic issue is deeper than that: how do we look up variables properly in a C<PRE> block whose C<OUTER> block hasn't even been invoked? == In conclusion A modest proposal: we already have excellent IoC primitives with C<nextsame> et al. Why not let DBC fall out of their use instead of trying to impose it from the outside? This poses no problems at all: class A { method foo($x) { PRE { $x < 10 } # ... } } class B is A { method foo($a, $b, $c) { PRE { [>] $a, $b, $c } callwith($c); # ... } } Both C<PRE> blocks get to run in their proper environment a the proper time. There's no magic anywhere. In fact, method-level C<PRE> and C<POST> cease to be a concept, C<PRE> and C<POST> submethods can be handled delicately in a corner of the MOP somewhere, the abomination that is C<CALL-VIA-DBC> goes away, and all that we're really left with are block-level C<PRE> and C<POST> phasers, which already seem like they could work. So, what do y'all think? [1] http://6guts.wordpress.com/2012/03/09/meta-programming-slides-and-some-rakudo-news/ [2] http://perlcabal.org/syn/S04.html#Phasers Expectantly, // Carl