Archive

Archive for the ‘Raku’ Category

Despotting

June 18, 2021 Leave a comment

API::Discord is a bit spotty. Many classes that are typically represented as a Str don’t sport that method. In same cases bare deparsed JSON is returned. That led to a fair amount of clutter that followed a pattern. After making a remote call via the API, I mix in a role and query the API further, before passing a list on via a Supply. I do the same operation with different operants. That’s sounds like a candidate for a multi sub.

multi sub augment(Discord::Channel $s) {
    $s does role :: {
        method Str { ‚#‘ ~ self.name }
    }
}

multi sub augment(%author where { .<discriminator>:exists }, API::Discord:D $discord) {
    $discord.get-user(%author<id>) does role :: {
        method Str { self.username ~ '#' ~ self.discriminator }
    }
}

multi sub augment(API::Discord::User $user) {
    $user does role :: {
        method Str { self.username ~ '#' ~ self.discriminator }
    }
}

# [...]

    start react {
        whenever $discord.ready {
            note "Logged in as { $discord.user.username }.";
        }
        whenever $discord.messages -> $message {
            next unless $channels ~~ $message.channel.name;

            $result.emit: [ $message.channel.&augment, $message.author.&augment, $message.content ];
        }
        whenever $discord.events -> $event {
            if $event<t> eq 'MESSAGE_UPDATE' {
                my $m = $event<d>;
                my $channel = $discord.get-channel($m<channel_id>).&augment;
                my $author = $m<author>.&augment($discord);
                next unless $channels ~~ $channel;

                $result.emit: [ $channel, $author, $m<content>, :update ];
            }
        }
        CATCH { default { say .^name ␣ .Str } }
    }

Simply calling augment as a pseudo-method will fix the shortcomings. If the API is changes I only have to accommodate that change in a single place in my program. Raku makes it very convenient to deal with an inconvenience. One might think Raku is British. Time to make some tea.

Categories: Raku

Matching nothing

June 14, 2021 Leave a comment

I requested and foolishly offered help with a Discord log bot to lizmat. While doing so I stumbled upon a neat use for none().

sub discord-log-stream(Mu:D :$channels is copy = %*ENV<DISCORD-CHANNELS> // none()) {
    $channels = $channels.split('#', :skip-empty).any;

    # ...

    start react {
        whenever $discord.messages -> $message {
            next unless $channels ~~ $message.channel.name;

            $result.emit: [ ‚#‘ ~ $message.channel.name, $message.author.&{ .username ~ ‚#‘ ~ .discriminator }, $message.content ];
        }
    }
}

If no channel list is given, the bot shall log all channels. Instead of special casing, I use an empty none-Junction as a default value. With channels to include, $channel contains an any-Junction of Str. Matching a single Str with ~~ will return True if the string is also in the any-Junction. Any test against none() will return True. So my default value will always test True. This is a little bit like matching against the inverse of the empty set.

As it happens none() is quite resilient. It survives both .split and .any. Being an instance of Junction makes it defined. That is helpful because it allows the :D-type smiley and as such guards against undefined values.

Given that none() is everything I would not wonder if one who masters Raku goes straight to निर्वाण.

Update:

Since none() is anything but obvious, adding a constant with a speaking name adds clarity.

constant all-channels := none();

sub discord-log-stream(Mu:D :$channels is copy = %*ENV<DISCORD-CHANNELS> // all-channels) {
    # ...
}

Categories: Raku

Teaching on a waterbed

June 13, 2021 1 comment

After watching Raku, the Big by Bruce Gray I can only agree with the assessment of how difficult it is to teach Raku to a beginner. I have been a Raku beginner myself since 2010. And that means learning the language can’t be hard. After all, even I managed. I had the Synopsis and IRC. The docs were pretty spotty and in some places outdated already (thanks to the GLR). Since there were no books or guides, the best I could do was start coding and asking questions. I’m quite happy with the result.

Things changed when I started to blog in earnest. With all those holes in the docs I had a lot of explaining to do. Let’s look at some code.

my &b = { 42 }; &b.say;
# OUTPUT: -> ;; $_? is raw = OUTER::<$_> { #`(Block|94577606390984) ... }

It’s just a block that returns 42. To explain the output I have to cover return values, default signatures, the topic, optional positional arguments, containers (one can’t explain the absence of a container without containers), default values and pseudo packages. And with all that I still skip my &b =.

I consider myself lucky, that as a beginner, I don’t need to know most of that stuff, before I can start coding. While looking at our website, one can only conclude, that being asked questions by beginners isn’t all the welcome. The link named “getting started” could be named better and be bigger.

We do have live logs now for IRC (logs for Discord have been requested). I can’t see any reason why they shouldn’t be placed prominently on the front page. Our chat channels are the entry point to the community. There is no reason to hide them (or us).

Bruce is right in asking for improvement, there is plenty of room for that. But the perspective of the educator is the wrong starting point. A linear course doesn’t work because of all the complexity hidden behind the defaults. Getting some order into a host of topics can’t hurt. Also, why is that an external link?

The community was my gateway to Perl 6Raku. I would love to see more help for folk with the same learning deficiency then I got.

Categories: Raku

Low profile quoting

June 1, 2021 5 comments

I wrote a program that got exactly one user that is not me and is used once a week. Hence, I can proudly claim to be 520% efficient. The result can be found at the bottom of each Raku Weekly News. While casting my Raku spells I once again had felt the urge for a simply but convenient way to inline fragments of html in code. The language leans itself to the task with colon pairs and slurpy arrays.

sub a(*@a, *%_) { ... }

a(:href<www.somewhere.tld>, child(...), 'bare string');

I don’t want o pull in lots of names into the local namespace and avoiding a module would be nice. For a simple shellish script unusual dependencies are best avoided. At first I tried to abuse packages but those are compile time creatures and I don’t fancy to pre-define all possible html tags. But a package in the end is just a Stash is a Map is a Hash. Building a simple dynamic Hash-like is quite easy.

constant term:<␣> = ' ';
constant term:<¶> = $?NL;

constant html = class :: does Associative {
    sub qh($s) {
        $s.trans([ '<'   , '>'   , '&' ] =>
                 [ '&lt;', '&gt;', '&amp;' ])
    }

    role NON-QUOTE {}

    method AT-KEY($key) {
        sub (*@a, *%_) {
            ('<' ~ $key ~ (+%_ ?? ␣ !! '') ~ %_.map({ .key ~ '="' ~ .value ~ '"'  }).join(' ') ~ '>' ~
            @a.map( -> \e { e ~~ NON-QUOTE ?? e !! e.&qh }).join('') ~
            '</' ~ $key ~ '>') does NON-QUOTE
        }
    }
}

put html<a>(:href<www.foo.bar>, html<em>('<person@domain.top>'), 'M&M');

Here we got a singleton of a class that does Associative and as such will react nicely to <> and {} subscripts. It will quote bare strings because the anonymous sub will mark its output with the empty role NON-QUOTE. Anything returned from that sub is HTML and as such doesn’t need quoting. Bare string will not be returned by that sub, resulting in them being quoted.

This snippet is short enough to be covered by fair use — even by German standards — so please feel free to use it.

UPDATE:

HTML-entities must not be quoted too and never have arguments. Since we can tell them apart by the & at the beginning, we can return something different from the hash-like.

constant html = class :: does Associative {
    sub qh($s) {
        $s.trans([ '<'   , '>'   , '&' ] =>
                 [ '&lt;', '&gt;', '&amp;' ])
    }

    role NON-QUOTE {}

    method AT-KEY($key) {
        when $key ~~ /^ '&' / {
            $key does NON-QUOTE
        }
        when $key ~~ /\w+/ {
            sub (*@a, *%_) {
                dd @a;
                ('<' ~ $key ~ (+%_ ?? ␣ !! '') ~ %_.map({ .key ~ '="' ~ .value ~ '"'  }).join(' ') ~ '>' ~
                @a.map( -> \e { e ~~ NON-QUOTE ?? e !! e.&qh }).join('') ~
                '</' ~ $key ~ '>') does NON-QUOTE
            }
        }
    }
}

put html<a>(:href<www.foo.bar>, html<em>('<person@domain.top>'), html<&nbsp;>, 'M&M');
put html<&nbsp;>;
Categories: Raku

The absence of a riddle

April 27, 2021 1 comment

Raku riddles have become popular. By chance, I came across a riddle generated by Rakudo — an emergent riddle so to speak.

class NotNil is Nil {};

class C {
    method NotNil() { NotNil.new }
};

sub s(NotNil()) {};

s(NotNil.new)

# OUTPUT: Impossible coercion from 'Nil' into 'NotNil': method NotNil returned a type object Nil

The culprit here is .new.

dd NotNil.new;
# OUTPUT: Nil

The constructor Nil.new will always return Nil. As lizmat pointed out, we need to provide our own constructor.

class NotNil is Nil {
    method new { self.CREATE }
}

The lesson I learned from this is that I can’t rely on inheritance for object creation. This is something we need to keep in mind when subclassing from a 3rd party module.

Categories: Raku

Reusing a wheel

April 21, 2021 1 comment

A while back I reinvented a wheel which is rolling slower then the one build into Rakudo. While reading BOOTSTRAP.nqp I discovered add_dispatchee. By stealing this wheel I might get better results. After all, this approach is working very well for manufacturers all over the world. Let’s have something to dispatch on.

my $emitter = Supplier.new;
my $s = $emitter.Supply;

my $p = start {
    loop {
        $emitter.emit: [42, 'answer', Any].roll;
        sleep 0.25;
    }
}

The Supply will spit out values we have to handle. We will need a proto to call add_dispatchee on and tap the Supply with.

sub handle-with(Supply:D $s, *@routines) {
    my proto pseudo-multi(|) {*}
    for @routines -> &b {
        &pseudo-multi.add_dispatchee(&b);
    }

    $s.tap: &pseudo-multi;
}

handle-with($s,
    sub (Int $_) { say .WHAT },
    sub (Str $_) { say .WHAT },
    sub (Any:U $_) { say 'undefined' },
);

await $p;

The multi method dispatcher does the heavy lifting and we might see the optimiser do some work too. We can’t use pointy blocks instead of subs because Rakudo wont let us. A multi sub without a handler for CX::Return doesn’t make sense.

All this is quite nice but comes with a catch. Roast doesn’t know know about add_dispatchee. So there might be a is implementation-detail missing. If that changes with new-dispatch, we will have to wait and see. I for one would like to see more possibilities for using dispatch in Raku.

Categories: Raku

Coercing the unspeakable

April 17, 2021 3 comments

My wish for typed Supply would be rather limited if we could not coerce to roles.

role R[::T] {}

class A {
    method m(R[Int]() $_) { say $_ ~~ R[Int] }
}

class B {
    method R[Int]() {}
}

# OUTPUT: Missing block
          at /home/dex/projects/raku/tmp/typed-supply.raku:35
          ------>     method R⏏[Int]() {}

So a Signature can ask for a coercion to a parametrised role but a class can’t provide such a method because the compiler doesn’t like the name. From the standpoint of the compiler method names are just strings. The class keyword is just veneer for the MOP.

B.^add_method('R[Int]', method {
    class :: does R[Int] {
    }.new
});
B.^compose;

A.new.m(B.new);

# OUTPUT: True

Having a dynamic compiler for a dynamic language does come with perks. However, using silly method names is not specced. So a problem solving issue is still in order.

Categories: Raku

All your idioms are belong to us

April 16, 2021 2 comments

In the closing thought in my last post I postulated the need to find idioms. That worried me a bit because finding things that are not there (yet) is no easy feat. By chance that day Hacker News linked to an article with well written and explained Python code. We can’t quite translate idioms from one language to another. But if we can steal ideasborrow features from other languages, maybe we can take inspiration for idioms too.

The article by Bart de Goede kindly links to a github repo, where we can find the following piece of code.

import requests


def download_wikipedia_abstracts():
    URL = 'https://dumps.wikimedia.org/enwiki/latest/enwiki-latest-abstract.xml.gz'
    with requests.get(URL, stream=True) as r:
        r.raise_for_status()
        with open('data/enwiki-latest-abstract.xml.gz', 'wb') as f:
            # write every 1mb
            for i, chunk in enumerate(r.iter_content(chunk_size=1024*1024)):
                f.write(chunk)
                if i % 10 == 0:
                    print(f'Downloaded {i} megabytes', end='\r')

This is a very basic implementation of wget. I know very little about Python but I doubt they would be able to implement a complex one for the lack of horizontal space. Python may just be the driving force behind the proliferation of 4K monitors. Being mean aside, the whole idea is that the HTTP component can return an iterator that produces chunks of a given size. Those are written to disk and a progress message is presented.

In Raku iterators are well hidden behind Seq. We also have threadsafe streams in the form of Supply and Channel. To take advantage of this hidden superpower, we need a HTTP client module that can return a Supply. As described in jnthn`s youngest video* we need to get a Supply via .body-byte-stream.

sub download_wikipedia_abstracts {
    use Cro::HTTP::Client;

    constant $abstract-url = 'http://dexhome/enwiki-latest-abstract.xml.gz';
    constant $abstract-file = '/tmp/enwiki-latest-abstract.xml.gz';

    sub MB(Int $i --> Str) {
        sprintf("%.2fMB", $i / 1024 ** 2)
    }

    sub has-time-passed(:$h = 0, :$m = 0, :$s = 1 --> Bool) {
        my $seconds = $h * 60*60 + $m * 60 + $s;
        state $last-time = now;

        if now - $last-time >= $seconds {
            $last-time = now;
            True
        } else {
            False
        }
    }

    with await Cro::HTTP::Client.get: $abstract-url -> $r {
        my $file-length = $r.header('content-length').Int // *;
        with open($abstract-file, :w, :bin) -> $fh {
            say "";
            react whenever $r.body-byte-stream -> Blob \data {
                LAST { progress; $fh.close };
                state $so-far += data.bytes;
                sub progress { print "\r{$so-far.&MB} of {$file-length.&MB} downloaded" }

                $fh.write: data;
                progress if has-time-passed;
            }
        }
    }
}

download_wikipedia_abstracts;

This Raku-version is a bit longer because it shows the correct size of the download in a nicer way. I should not have made fun of Python for its horizontalism. We are only marginally better here. And not just with indentation. This is hardly readable boilerplate rich code. As functions are verbs and objects are nouns, we can try to be a bit more literate.

sub download_wikipedia_abstracts {
    use Cro::HTTP::Client;

    constant $abstract-url = 'http://dexhome/enwiki-latest-abstract.xml.gz';
    constant $abstract-file = '/tmp/enwiki-latest-abstract.xml.gz';

    my $r = await Cro::HTTP::Client.get: $abstract-url;

    my $file-length = $r.header('content-length').Int // *;
    my $fh = open($abstract-file, :w, :bin);
    print "\e[2Kdownloading abstract ";

    $r.body-byte-stream
        ==> progress-indicator(:max-bytes($r.header('content-length').Int // *), :bar-length(20))
        ==> supply-to-file(:path($abstract-file.IO));
}

By using the (sadly underused) feed operator we can nicely show the flow of data of the byte stream through our program. Since Boilerplate never really goes away, we merely put it someplace else.

constant NOP = -> | {;};

multi sub progress-indicator(Supply:D $in, :$max-bytes!, :$bar-length = 10, :&prefix = NOP, :&suffix = NOP, :&speed is copy --> Supply:D) {
    constant @block-chars = (0x2589 .. 0x258F, 0x2591).flat.reverse».chr;
    constant &store-cursor = { print "\e[s" }
    constant &restore-cursor = { print "\e[u" }
    constant &hide-cursor = { print "\e[?25l" }
    constant &show-cursor = { print "\e[?25h" }
    constant &reset-terminal = { print "\ec" }

    sub mega-bits-per-second($so-far) {
        state $last-time = now;
        state $last-bytes = 0;

        if now > $last-time + 2 {
            print ' ', (($so-far - $last-bytes) / (now - $last-time) / 1024**2 * 8).fmt('%.2fMBit/s');

            $last-bytes = $so-far;
            $last-time = now;
        }
    }

    my $out = Supplier::Preserving.new;
    &speed //= &mega-bits-per-second;

    hide-cursor;

    start react whenever $in -> \v {
        LAST { show-cursor; $out.done; }

        state $so-far += v.bytes;
        my $percent = $so-far / $max-bytes * 100;
        my $fraction = (($percent / $bar-length - floor $percent / $bar-length) * 7).round;

        store-cursor;
        prefix $so-far, $max-bytes;

        print '[', @block-chars[*-1] x floor($percent / 100 * $bar-length), ($percent < 100 ?? @block-chars[$fraction] !! ''), "\c[0x2591]" x ($bar-length - floor($percent / 100 * $bar-length)) - 1, ']';

        # &speed ?? speed($so-far) !! mega-bits-per-seconds($so-far);
        speed($so-far);

        suffix $so-far, $max-bytes;

        restore-cursor;

        $out.emit: v;
    }

    $out.Supply
}

multi sub supply-to-file(Supply:D $in, IO::Path :$path, :$blocking) {
    # $io = $io.open(:w, :bin) if $io ~~ IO::Path;
    my $io;

    react whenever $in -> \v {
        state $first = True and ( $first = False; $io = v ~~ Blob ?? $path.open(:w, :bin) !! $path.open(:w) );
        $io.write: v;
    }
}

The feed operator calls its RHS with the return value of the LHS as the last positional parameter. By using named arguments for our stream processors, we end up with just one positional and are a little more descriptive for the (often optional) options. The progress indicator is using NOP to allow :speed(NOP) to disable mega-bits-per-second. Having &prefix and &suffix is done in hopes to make functional programming easy. Checking the type of the first chunk against Blob to decide if we need to open the file in binary mode is a hack. I’m not happy with that.

Sadly Supply and Channel are not typed. They will pass whatever value is emitted to the other side. If they would be parametrised roles that default to Mu I would offload that decision into multi-dispatch. The 2nd hack that emulates FIRST in the react block would also go away. Further, Cro and other stream producers would be easier to document --> Supply[Blob] would suffice. By moving the type check to .emit, consumers of streams would not need to worry about getting the wrong type. Any failed type check would happen closer to the point where the wrong value is produced. Hunting down errors in concurrent code is not fun. Any help is well worth it in this reguard.

As a module author we can mitigate that design shortcoming with mixins.

role Typed[::T] {
    method of { T }
}

role WithLength[$length = *] {
    has $.length = $length;
}

constant BlobSupply = Typed[Blob];

my $s = (Supplier.new.Supply but Typed[Blob]) but WithLength[42];

multi sub f(Supply $s where * ~~ Typed[Blob] & WithLength) {
    say „I got Blobs with a total size of {$s.length} byes.“;
}

multi sub f(Supply $s) {
    fail(‚No can do!‘);
}

f $s;

With the new dispatcher where-clauses are going to be less slow. Yet, a solution in CORE would be much better. As a proper language feature it would get more use. IO::Handle does provide a Supply for read but not for write operations. With typed streams this would be much easier to implement.

Idioms are formed in natural languages to make communication more efficient and precise at the same time. I believe the same is true for PLs. The best idioms are those that are easy to guess the meaning of. With the help of the feed operator and good names that seams to be quite possible. Which leaves the question where we document our idioms. So this needs more thought. Pretty much the only good thing about this pandemic is that we all got more time to do so. The less distractions, the better. I think I gonna play a game now. :-D

*) I was about to write “last video”, what, surprisingly, constitutes as valid English. But we don’t want that. jnthn, please moar of the same! Your VMs and videos are really good.

Categories: Raku

Raku is a match for *

March 11, 2021 2 comments

PimDaniel asked an interesting question.

How do i test match is True while matching : this does NOT work :
if my ($type,$a,$b,$c) = ($v ~~ /^ ('horiz'|'vertic') '_' (\d+) '_' (\d+) '_' (\d+) $/)>>.Str { ... }
Well i made it in 2 times 1/ capture and test the match, 2/ convert the match to Str.

There was no prompt answer and no improvement at all. I couldn’t find a nice way to do this quickly either. In fact it took me the better part of an hour to crack this nut. The main issue here is that a failed match will produce Nil that .Str will complain about. So lets separate boolean check of if and the conversion to Str.

my $a = '1 B';

if $a ~~ /(<digit>) \s (<alpha>)/ -> $_ {
    my ($one, $B) = .deepmap: *.Str;
    say "$one $B";
}
# OUTPUT: 1 B

By forcing the result of the condition expression into the topic, we can run any method on the result of the match, but only if Match.bool returns true. I don’t got a degree in CS* but would be very surprised if Raku-signatures would not turn out to turing complete.

if $a ~~ /(<digit>) \s (<alpha>)/ -> Match (Str() $one, Str() $B) {
    dd $one;
    dd $B;
}
# OUTPUT: "1"
          "B"

The signature of the if block coerces the Match to a list. We pick two elements of it and coerce those to Str. Of cause we could coerce to anything we like based on the position of the captures.

Regexes in Raku are compiled to the same byte code then the rest of the program. In fact grammars are just classes with a funky syntax. That’s why we can run Raku code inside a regex with ease. That means we can turn the whole thing inside out.

my @a = <1 B 3 D 4>;
my @b;

my $hit;

for @a -> $e {
    @b.push: ($e ~~ /(<alpha>) || { next } /).Str;
}

say @b;
# OUTPUT: [B D]

Here we skip the .push if the match does not succeed by skipping the rest of the loop body with next. We could fire any control exception inside the regex. That means we could stick the whole thing into a sub and return the value we are looking for from within the regex.

sub cherry-pick-numeric(Str $matchee) {
    $matchee ~~ m/(<digit>) && { return .Numeric }/;
    Empty
}

@b = do .&cherry-pick-numeric for @a;

dd @b;
# OUTPUT: Array @b = [1, 3, 4]

Raku has been in the making for 10 years. This was an gargantuan task. Now comes the hard bit. We have to take that large language and find all the nice idioms. Good things come to those who wait (on IRC).

*) read: Don’t believe anything I write. You have been warned.

Update:

In truly lazy fashion I came up with a way to turn a match into a lazy list after the work should have been done.

$a = '1B3D4';

my \ll := gather $a ~~ m:g/
      [ <alpha> && { take $/<alpha>.Str } ]
    | [ <digit> && { take $/.<digit>.Numeric } ]
    | [ { say 'step' } ]
/;
say ll[0];
say ll[3];
# OUTPUT: 1
          step
          step
          step
          D

The trick is force the match to run all the way to the end of the string with the :g adverb. This run will be interrupted by a take (by throwing CX::Take) and resumed when the next value is asked from the Seq returned by gather. I don’t know if this is memory efficient thought. There may be a Match instance kept around for each take.

Categories: Raku

Undocumented escape hatch

February 28, 2021 1 comment

On my quest to a custom when-statement I did quite a bit of reading. The study of roast and Actions.nqp can lead to great gain in knowledge.

$ less -N S04-statements/given.t
136 # given returns the correct value:
137 {
138      sub ret_test($arg) {
139        given $arg {
140          when "a" { "A" }
141          when "b" { "B" }
142        }
143      }
144
145     is( ret_test("a"), "A", "given returns the correct value (1)" );
146     is( ret_test("b"), "B", "given returns the correct value (2)" );
147 }

As we can see in this example, the spec asks given to return the value provided to succeed. This is an ENODOC. We don’t have to depend on sink to turn the succeed value into a Routines return value.

my $result = do given 'a' {
    CONTROL { default { say 'seen ', .^name } }
    when Str { say 'Str'; succeed('It was a string.'); }
}
dd $result;
# OUTPUT: Str
          Str $result = "It was a string."

It’s a bit odd that we need the do thought, as given will always return at least Nil. The oddity doesn’t stop there. We can get hold of control exceptions. Some of which can return a value. That value is well hidden in nqp-land. Control-exceptions are clearly not an implementation details. So there is no reason for that limitation. Let’s remove it.

given 'a' {
    succeed .Str;
    CONTROL {
        when CX::Succeed {
            use nqp;
            my $vmex := nqp::getattr(nqp::decont($_), Exception, '$!ex');
            my $payload := nqp::getpayload($vmex);
            say 'seen succeed with payload: ', $payload;
        }
        default { say 'seen ', .^name; }
    }
}
# OUTPUT: seen succeed with payload: a

My expedition into nqp-land where started by the discovery, that CX::Succseed and CX::Proceed are swallowed by a hidden monster.

given 'a' {
    CONTROL { default { say 'seen ', .^name } }
    when Str { say 'Str'; succeed('It was a string.'); }
}
# OUTPUT:
$ less -N src/Perl6/Actions.nqp
9932     sub when_handler_helper($when_block) {
9933         unless nqp::existskey(%*HANDLERS, 'SUCCEED') {
9934             %*HANDLERS<SUCCEED> := QAST::Op.new(
9935                 :op('p6return'),
9936                 wrap_return_type_check(
9937                     QAST::Op.new(
9938                         :op('getpayload'),
9939                         QAST::Op.new( :op('exception') )
9940                     ),
9941                     $*DECLARAND) );

The first when or default clause add a fresh handler and only checks for SUCCEED and ignores any CONTROL blocks already present. Given that intercepting X::Control is specced, this is rather surprising.

Alas, adding exception handlers via macros, doesn’t work right now. This is not a pressing issue because macros are subject to change my RakuAST anyway and I might get the desired result with a Slang.

Categories: Raku