Reputation: 23517
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
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
.
default
blockwhen
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
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
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