jjmerelo
jjmerelo

Reputation: 23517

CONTROL or once messes with last?

This loop never stops:

class CX::Vaya does X::Control {
    has $.message
}

loop {
    once {
        CX::Vaya.new( message => "I messed up!" ).throw;
    }
    last;
    CONTROL {
        default {
            say "Controlled { .^name }: { .message }"
        }
    }
}

It keeps writing

Controlled CX::Last: <last control exception>
Controlled CX::Last: <last control exception>
Controlled CX::Last: <last control exception>
Controlled CX::Last: <last control exception>
Controlled CX::Last: <last control exception>
Controlled CX::Last: <last control exception>
Controlled CX::Last: <last control exception>
Controlled CX::Last: <last control exception>
...

It could be the once bit, because CONTROL phaser with last does finish:

loop { say "Hey"; last; CONTROL { default: .message.say } }
# OUTPUT: «Hey␤<last control exception>␤»

But I'm not really sure.

Upvotes: 5

Views: 99

Answers (3)

Brad Gilbert
Brad Gilbert

Reputation: 34120

It is really the combination of CONTROL and CX::Succeed that are preventing the default behaviour of last.


All of the flow control functions are implemented in terms of exceptions.

Rather than lump them in with regular exceptions in CATCH, there is a special handler just for them named CONTROL.

Both CATCH and CONTROL can be thought of as special forms of given.
The two main differences being that the topic is set to the exception, and that something special happens if there was a CX::Succeed.

If there is a CX::Succeed then that indicates the exception or flow-control was successfully handled, and that no further processing needs to happen.


There are two ways to get CX::Succeed to happen inside of CONTROL.

  1. default block
  2. when block

(Technically I think that just plain succeed should also work, but it doesn't seem to.)

If you are in one of those blocks, and you want the processing to actually continue, you can use proceed (CX::Proceed).

loop {
    once {
        CX::Vaya.new( message => "I messed up!" ).throw;
    }
    last;

    CONTROL {
        default {
            .^name.say;
            proceed if CX::Last;
        }
    }
}

It is much better to just use a when block that captures exactly what you want to handle specially.
(Note that a postfix when does not throw CX::Succeed, so normal processing would continue in that case.)


default is a block prefix. Meaning it always must be followed by a block.

Adding just : to the end of an identifier is only used to create a label, and on method calls to remove the parentheses.

So default: does absolutely nothing here.

Also once is not a block prefix. It is a statement prefix. (It just so happens that a block can be treated as a statement.)
There is no reason to add a block there unless you are going to write more than one statement inside of it.


loop {
    once CX::Vaya.new( message => "I messed up!" ).throw;
    last;

    CONTROL {
        when CX::Vaya {
            .^name.say;
        }
    }
}

Upvotes: 4

raiph
raiph

Reputation: 32404

What Jonathan says.

In more detail:

  • The default control flow for P6 is that the next statement follows the current one. So say 42; say 99; defaults to doing the say 42 and then doing the say 99.

  • P6 has an advanced and pervasive exception system that's used for any exception to this default control flow of one-statement-then-the-immediately-following-statement. It's not just for errors.

  • Any exception to the default control flow is called an exception. Many are error exceptions. But there's another category, namely control flow exceptions, or control exceptions for short. For example, an explicit or implicit return/leave from a routine/block is a control exception. (In principle. In reality the compiler/optimizer will elide some of this machinery when appropriate.)

  • A last is a control exception. It throws the control exception CX::Last. Its message payload is "last control exception". You are seeing that message.

  • A CONTROL block is entered if a control exception applies to its containing block. It can either handle the passed exception or not handle it. If it handles it then control resumes with the statement that follows it. If, as in this example, the CONTROL block is at the end of a loop, then the next statement would be an implicit next (another control exception) and the loop would restart.

  • The code CONTROL { default { say "Controlled { .^name }: { .message }" } handles all control exceptions, including the one thrown by last. So it prints its message and then the loop continues. (Infinitely.)

  • In the code loop { say "Hey"; last; CONTROL { default: .message.say } } # OUTPUT: «Hey␤<last control exception>␤», the CONTROL block does display the exception's message but it doesn't handle it. (default: is just a label, no different to, say, I'm-a-label:, and unrelated to default { ... }.) So the last control exception isn't handled by your code and instead is handled by the default language handling which is to break out of the loop.

Upvotes: 7

Jonathan Worthington
Jonathan Worthington

Reputation: 29454

Loop flow control in Perl 6 is implemented using control exceptions. Thus, last is actually throwing a CX::Last control exception. Since the CONTROL block uses default, it is therefore catching the CX::Last thrown by last, meaning that control is never transferred out of the loop.

The fix is to instead specify which control exception to catch, using when:

loop {
    once {
        CX::Vaya.new( message => "I messed up!" ).throw;
    }
    last;
    CONTROL {
        when CX::Vaya {
            say "Controlled { .^name }: { .message }"
        }
    }
}

Upvotes: 13

Related Questions