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

Reply via email to