Archive

Archive for the ‘Raku’ Category

Coercive bits

July 13, 2022 1 comment

Altreus was looking for a way to convert a list of bitmask names, provided as a string by the user, to a bitmask. He wished for BUILDARGS, as provided by Perl’s Moose but was given good advise how to solve the problem without making object instantiation even more complex. Nobody can stop me from doing just that.

With Moose, BUILDARGS allows to modify values before they are bound to attributes of a class. We can do the same by using the COERCE-protocol, not just for Routine-arguments.

role BitMask[@names] {
    has int $.mask;
    sub names-to-bits(@a) {
        my int $mask;
        for @a -> $s {
            $mask = $mask +^ (1 +< @names.first($s, :k) // fail('bad‼'))
        }

        $mask
    }
    sub bits-to-names($mask) {
        ($mask.base(2).comb.reverse Z @names).map: -> [Int() $is-it, $name] { $is-it ?? $name !! Empty }
    }

    multi method COERCE(Str $s) { self.COERCE: $s.split(' ').list }
    multi method COERCE(List $l) { self.new: :mask($l.&names-to-bits) }

    method raku { "BitMask[<@names[]>](<{$!mask.&bits-to-names}>)" }
    method bits(--> Str) { $.mask.fmt('%b') }
}

class C {
    has BitMask[<ENODOC ENOSPEC LTA SEGV>]() $.attr is required;
}

my $c = C.new: :attr<ENODOC ENOSPEC SEGV>;
say $c;
say $c.attr.bits;
# OUTOUT: C.new(attr => BitMask[<ENODOC ENOSPEC LTA SEGV>](<ENOSPEC LTA SEGV>))
#         1011 

Here I build a role that carries the names of bits in a bit-field and will create the bit-field when give a Str or List containing those names. Since I use a parametrised role, my bitfield is type-safe and the type is tied to the actual names of the bits. As a consequence a user of the module that exports C can extend the accepted types by using that specific role-candidate.

enum IssueTypes ( ENODOC => 0b0001, ENOSPEC => 0b0010, LTA => 0b0100, SEGV => 0b1001 );

class IssueTypesBits does BitMask[<ENODOC ENOSPEC LTA SEGV>] {
    method new(*@a where .all ~~ IssueTypes) {
        self.bless: mask => [+|] @a
    }
}

sub bitmask(*@a where .all ~~ IssueTypes) {
    IssueTypesBits.new: |@a
}

my $c3 = C.new: attr => BEGIN bitmask(ENODOC, LTA, SEGV);
say $c3;
say $c3.attr.bits;
# OUTPUT: C.new(attr => BitMask[<ENODOC ENOSPEC LTA SEGV>](<ENODOC LTA SEGV>))
          1101

Here I define an enum and provide the same names in the same order as in does BitMask. With the shim bitmask I create the type-safety bridge between IssueTypes and BitMask[<ENODOC ENOSPEC LTA SEGV>].

It is very pleasing how well the new COERCE-protocol ties into the rest of the language, because we can use a coercing-type at any place in the source code which takes a normal type as well. What is not pleasing are the LTA-messages X::Coerce::Impossible is producing.

my $c4 = C.new: attr => 42;
# OUTPUT: Impossible coercion from 'Int' into 'BitMask[List]': method new returned a type object NQPMu
            in block <unit> at parameterised-attribute.raku line 60

This is surpring because we can get hold of the candidates for COERCE at runtime.

CATCH {
    when X::Coerce::Impossible && .target-type.HOW === Metamodel::CurriedRoleHOW {
        put "Sadly, COERCE has failed for {.from-type.^name}. Available candidates are: ", .target-type.new.^lookup('COERCE').candidates».signature».params[*;1]».type.map(*.^name)
    }
}

# OUTPUT: Sadly, COERCE has failed for Int. Available candidates are: Str List

What we can’t see are the candidates of IssueTypesBits, because that is hidden behind the constructor new. I tried to use the MOP to add another multi candidate but failed. When parametrising the underlying type-object gets cloned. Any change to the multi-chain wont propagate to any specialised types.

The COERCE-protocol is quite useful indeed. Maybe it’s time to document it.

Categories: Raku

The truth is a hard problem

July 11, 2022 2 comments

In a recent article, stevied promised a detailed walk through of code. I asked him if he would be interested in a critique of his writings. He foolishly agreed.

He claims that Arrays are eager and fill all memory if given the chance. Let’s check this claim.

my @a = 1, 1, * + * … ∞;
say @a.WHAT;

# OUTPUT: (Array)

Array.STORE goes out of it’s way to maintain iterables (not all things that are iterable do Iterable) and only reify container slots that we actively ask for. It will cache lazy lists by default, assuming that when we use .AT-POS (usually indirectly with a positional subscript) we want to use the values again. Dealing with lazy lists is a hard problem and many get it wrong (as my next blog post will discuss in more detail).

In the next paragraph I found another inaccurate statement. Int:D does not check for definedness.

multi sub bar(Any:D $) { say 'defined'; }
multi sub bar(Any:U $) { say 'undefined'; }

bar(Int); # undefined

class C { method defined { False } }
my $c = C.new; # undefined
bar(C); # undefined
bar($c); # defined
say defined $c; # False
say $c.DEFINITE; # True
say $c // 'undefined'; # undefined

In Raku objects are either type objects or definite objects. The latter are the result of nqp::create. Raku and most user code expects objects to provide a basic set of methods that can be found in Mu or Any. The default type-check is against Mu. We can check if an object is definite with the macro .DEFINITE (you can overload a method of the same name, but Rakudo will ignore it) or, if we also want to know if we got an ordinary object, with $foo ~~ Mu:D. There is a clear distinction of definedness and being definite. Raku needs to make that differentiation to support “unusual” values and types like Nil and Failure. Please note, that type-objects are not definite but can be defined. Types are a hard problem.

In the same paragraph stevied writes: “defined integer object”, even though he makes a type-check against Int.

multi sub foo(Int:D $i) { }
multi sub foo($i where { $i - $i.truncate == 0 }) { say 'lolwut‽' }
multi sub foo($) { say 'not integer'; }

foo(1.0);
foo(1/1);
foo(¼);

# OUTPUT: lolwut‽
          lolwut‽
          not integer

Int:D will fail for any value that doesn’t got (Int) in its .^mro or is a subset of Int or any of Int‘s sub-classes. Raku sports an excellent coercion protocol and there is no reason not use it.

subset PositiveInteger of Numeric:D() where { $^i > 0 && ($i - $i.truncate == 0) || fail("Expected a positive and an integer value but got $i.")}
sub get-prime(PositiveInteger $nth where * > 0) {
    say ($x.grep: *.is-prime)[$nth - 1];
}
get-prime('5');
get-prime(½);
# OUTPUT: 11
          Expected a positive and an integer value but got 0.5.
            in block  at 2021-03-08.raku line 1809
            in block <unit> at 2021-03-08.raku line 1288

A value check is needed in this instance because .AT-POS will coerce to Int (by truncating) what may give a reasonable answer for an unreasonable question. No matter how sharp the knife, we wont get the ½th prime number. Being positive and integer is a hard problem.

Please don’t use say unless you actually want to run .gist.

say Nil;
put Nil;
# OUTOUT: Nil
          Use of Nil in string context
            in block  at 2021-03-08.raku line 1818

When you say you may miss subtile bugs, especially when running CI-tests.

Further down we find Sequence but grep returns a Seq – in this example. I never had to make the distinction between Seq and HyperSeq but the latter is not a child of Cool so a lot of interfaces are missing. The same is true for the role Sequence. The build-in types are a hard problem.

If we must ask what exactly * is (I wouldn’t, because that’s a really hard question.), it is best to provide the correct answer.

my &b = * + *;
say .WHAT, .signature with &b;
my &b2 = { $^a + $^b };
say .WHAT, .signature with &b2;
# OUTPUT: (WhateverCode)(;; $whatevercode_arg_11 is raw, $whatevercode_arg_12 is raw)
          (Block)($a, $b)

A WhateverCode-object will have all its arguments as is raw, what means it will always have access to the containers used by the callee (There are many things we can do, but probably shouldn’t, with .VAR). It will never have control exceptions (no return or phasers, etc.), doesn’t have a proper scope (no use-statement) and a few more things. The idea is to have a subclass of Code that is so simple that the compiler can always inline it. Code-objects are a hard problem.

I would have written that code a little different.

sub get-primes(*@nth) {
    (^∞).hyper.grep(-> Int:D() $_ is raw { .is-prime })[@nth »-» 1]
}

.put for get-primes(5, 50, 500, 5000, 50000);

I spend a pretty penny on that Threadripper, so I better .hyper as often as I can. If sensible I try to use a slurpy to move the burden of dealing with plurals from the user to the implementer. We can ask .AT-POS for a list of values, so there is no reason not to.

There are quite a few more inaccuracies in Steve’s article. Bless him, Raku is a bitch and we are not good at explaining how the language actually works. For most programs that doesn’t matter, they will run just fine even if the programmer is sloppy. I learned most of the gritty details when tracking down bugs. Granted, I stick my arm deep into the machinery, so it’s my own bloody fault when I get pinched. Since bugs happen we need to get better at explaining how to hunt them down. Raku is a hard problem.

Categories: Raku

Sinking Errors

June 26, 2022 1 comment

I was looking for a way to output debug messages that can also carry additional values, when not output to the screen. That is easy. The tricky part is golfing the interface. After quite a bit of struggle. I ended up with the following.

use Log;

dd ERROR('foo') ~~ LogLevel::ERROR;
dd ERROR('foo') ~~ LogLevel::DEBUG;
say ERROR('foo').&{ .file, .line };
VERBOSE 'Detailed, very much so indeed.';
my $*LOGLEVEL = 2;
VERBOSE 'Detailed, very much so indeed.';

# OUTPUT:
# Bool::True
# Bool::False
# (2021-03-08.raku 1661)
# 2021-03-08.raku:1664 Detailed, very much so indeed.

In sink-context, the “functions” will output to $*ERR and when not sunk return an object that provides a few useful methods. To get both, we need to implement the methods sink and CALL-ME.

# Log.rakumod

use v6.d;

my role Functor {
    has $.message;
    my $.level;
    has $.captured-loglevel;
    has $.file;
    has $.line;
    method CALL-ME(*@message) {
        my ($line, $file) = callframe(1).&{ .line, .file };
        self.new: message => "$file:$line " ~ @message.join($?NL), :captured-loglevel($*LOGLEVEL // 0), :$line, :$file;
    }
    method sink { $*ERR.put(self.message) if ($!captured-loglevel // 0) ≥ $.level }
    method Str(::?CLASS:D:) { self.message }
    method gist(::?CLASS:D:) { self.^shortname ~ ' ' ~ self.message }
}

my $ERROR;                                                                                                                                                                    my $VERBOSE;
my $DEBUG;

class LogLevel {
    constant ERROR := $ERROR;
    constant VERBOSE := $VERBOSE;
    constant DEBUG := $DEBUG;
}

$ERROR = class :: is LogLevel does Callable does Functor { my $.level = 1; }
$VERBOSE = class :: is LogLevel does Callable does Functor { my $.level = 2; }
$DEBUG = class :: is LogLevel does Callable does Functor { my $.level = 3; }

sub EXPORT {
    Map.new: '&ERROR' => $ERROR,
             '&VERBOSE' => $VERBOSE,
             '&DEBUG' => $DEBUG,
             'LogLevel' => LogLevel,
}

To behave like a function a Callable must be bound to an &-sigiled symbol (or Rakudo complains when we use it as a term). As soon as Rakudo spots a class, it will stuff it into UNIT::EXPORT::ALL. When importing any &-sigiled key of sub EXPORT, with the same basename as a class with that basename, the symbol from EXPORT will be ignored. The only way to avoid that is to have anonymous classes and do the forward-declarations by hand. The latter is needed to allow ERROR('foo') ~~ LogLevel.

All that is a bit convoluted but works as expected. What I didn’t expect, is that dynvars that are user-defined are not visible in method sink. Not much of a problem in my case, as capturing the state of $*LOGLEVEL inside CALL-ME is the right thing to do anyway. What threw me off is the inconsistency with other functions and methods that are called by a time-machine. We type BEGIN, BUILD and friends in all-caps for that very reason. This may warrant a problem solving issue. It’s an ENODOC for sure.

I believe golfing APIs is a most desireable effort. Laziness isn’t just a virtue for programmers, often it’s a given.

Categories: Raku

Reducing sets

May 25, 2022 1 comment

This week´s PWC asks us for hexadecimal words and distinctly different directories.

sub term:<pwc-166-1> {
    sub less-substitutions-then ($_, $n) {
        .subst(/<-[olist]>/, :g, '').chars < $n
    }

    '/usr/share/dict/words'.IO.words.race\
        .grep({.chars ≤ 8 && .&less-substitutions-then(4)})\
        .map(*.trans(<o l i s t> => <0 1 1 5 7>))\
        .grep(/^ <[0..9 a..f A..F]>+ $/)\
        .sort(-*.chars)\
        .say;
};

Once again, we write the algorithm down. Get the words, drop anything longer then 8 chars or what would need more then 4 substitutions. Then do the substitutions and grep anything that looks like a hexadecimal numeral. Sort for good measure and output the first 100 elements.

The second task provides us with a little challenge. We need to mock listing directories and working with them. Since dir returns a sequence of IO::Path I can create those by hand and mixin a role that mimics some filesystem operations. I can overload dir to provide a drop-in replacement.

sub term:<pwc-166-2> {
    sub basename(IO::Path $_) { .basename ~ (.d ?? '/' !! '') }
    sub pad(Str $_, $width, $padding = ' ') { .Str ~ $padding x ($width - .chars) }

    sub dir(Str $name) {
        sub mock-file(*@names) { @names.map({ IO::Path.new($_) but role :: { method f ( --> True ) {}; method e ( --> True ) {} } } ) }
        sub mock-dir(*@names) { @names.map({ IO::Path.new($_) but role :: { method d ( --> True ) {}; method e ( --> True) {} } }) }

        constant %dirs = dir_a => flat(mock-file(<Arial.ttf Comic_Sans.ttf Georgia.ttf Helvetica.ttf Impact.otf Verdana.ttf>), mock-dir(<Old_Fonts>)),
                         dir_b => mock-file(<Arial.ttf Comic_Sans.ttf Courier_New.ttf Helvetica.ttf Impact.otf Tahoma.ttf Verdana.ttf>),
                         dir_c => mock-file(<Arial.ttf Courier_New.ttf Helvetica.ttf Impact.otf Monaco.ttf Verdana.ttf>);

        %dirs{$name}
    }

    sub dir-diff(+@dirs) {
        my @content = @dirs».&dir».&basename;
        my @relevant = (([∪] @content) ∖ [∩] @content).keys.sort;

        my @columns = @content.map(-> @col { @relevant.map({ $_ ∈ @col ?? $_ !! '' }) });
        my $col-width = [max] @columns[*;*]».chars;

        put @dirs».&pad($col-width).join(' | ');
        put (''.&pad($col-width, '-') xx 3).join('-+-');
        .put for ([Z] @columns)».&pad($col-width)».join(' | ');
    }

    dir-diff(<dir_a dir_b dir_c>);
};

I’m asked to add a / to directories and do so with a custom basename. The rest is liberal application of set theory. Only names that don’t show up in all directories are relevant. Columns are created by matching the content of each directory against the relevant names. The width of columns is the longest string. The header is put on screen. To output the columns line by line, x and y are flipped with [Z].

After careful study of the solutions written in other languages, I believe it is fair to call Raku an eco-friendly language. Our keyboards are going to last at least twice a long.

Categories: Raku, Uncategorized

Writing it down

May 21, 2022 2 comments

PWC 165 refers us to mathsisfun for the algorithm to be used. Let’s write it down.

    my $input = '333,129  39,189 140,156 292,134 393,52  160,166 362,122  13,193
                341,104 320,113 109,177 203,152 343,100 225,110  23,186 282,102
                284,98  205,133 297,114 292,126 339,112 327,79  253,136  61,169
                128,176 346,72  316,103 124,162  65,181 159,137 212,116 337,86
                215,136 153,137 390,104 100,180  76,188  77,181  69,195  92,186
                275,96  250,147  34,174 213,134 186,129 189,154 361,82  363,89';

    my @points = $input.words».split(',')».Int;

    my \term:<x²> := @points[*;0]».&(*²);
    my \xy = @points[*;0] »*« @points[*;1];
    my \Σx = [+] @points[*;0];
    my \Σy = [+] @points[*;1];
    my \term:<Σx²> = [+] x²;
    my \Σxy = [+] xy;
    my \N = +@points;

    my $m = (N * Σxy - Σx * Σy) / (N * Σx² - (Σx)²);
    my $b = (Σy - $m * Σx) / N;

    say [$m, $b];

That was exceedingly simple. If the point cloud is large, this might also be exceedingly slow. There are no side effects and everything is nice and constant. Should be easy to get the CPU busy.

    sub delayed(&c) is rw {
        my $p = start { c };
        Proxy.new: STORE => method (|) {}, FETCH => method { await $p; $p.result }
    }

    my \term:<x²> = delayed { @points[*;0]».&(*²) };
    my \xy = delayed { @points[*;0] »*« @points[*;1] };
    my \Σx = delayed { [+] @points[*;0] };
    my \Σy = delayed { [+] @points[*;1] }
    my \term:<Σx²> = delayed { [+] x² };
    my \Σxy = delayed { [+] xy };
    my \N = +@points;

    my $m = (N * Σxy - Σx * Σy) / (N * Σx² - (Σx)²);
    my $b = (Σy - $m * Σx) / N;

    say [$m, $b];

Since I use sigil-less symbols, I get binding for free. This in turn makes using Proxy easy. The await $p part is called more then once per Proxy but not in a loop so there is no real need to optimise here. On my box the Proxyed version is almost twice as fast. A nice win for such a simple change.

I somehow feel this solution might please a mathematician — as Raku should.

Categories: Raku

Antipairing

May 1, 2022 1 comment

As suspicious questions on IRC and Discord revealed, there are quite a few solutions to the PWC that are not made public. One question in particular indicates that there is a build-in missing in Raku.

Nemokosch: let’s say I have a 5×5 table for some reason

PWC162-2 is asking to implement an encryption algorithm that uses a simple substitution table. Having a data-struction that allowes to turn a character into a 2-dimensional @index and then use it to @replacement[||@index] would be very helpful indeed. We can use .antipairs to turn a simple list into something we can assign to a Hash and be done with it. With 2-dimension, we have to create our own.

proto sub deepantipairs(@positional, $dimensions --> Positional) {*}
multi sub deepantipairs(@a, 2) {
    @a.pairs.map({ my $outer-key = .key; .value.antipairs.map({ .key => ($outer-key, .value) }) }).flat
}
my $phrase = 'Spring has sprung!';
my @table = flat($phrase.lc.comb.grep(/\w/), 'a'..'z').unique[^25].batch(5);
my %antitable = @table.&deepantipairs(2);

# OUTPUT:

# Array @table = [("S", "p", "r", "i", "n"), ("g", "h", "a", "s", "u"), ("!", "b", "c", "d", "e"), ("f", "j", "k", "l", "m"), ("o", "q", "t", "v", "w")]
# Hash %antitable = {"!" => $(2, 0), :S($(0, 0)), :a($(1, 2)), :b($(2, 1)), :c($(2, 2)), :d($(2, 3)), :e($(2, 4)), :f($(3, 0)), :g($(1, 0)), :h($(1, 1)), :i($(0, 3)), :j($(3, 1)), :k($(3, 2)), :l($(3, 3)), :m($(3, 4)), :n($(0, 4)), :o($(4, 0)), :p($(0, 1)), :q($(4, 1)), :r($(0, 2)), :s($(1, 3)), :t($(4, 2)), :u($(1, 4)), :v($(4, 3)), :w($(4, 4))}

Let’s rotate the table (the basic idea behind the Enigma) to create a much more interesting cypher-text.

sub encrypt($phrase, $text --> Str) {
    my @table = flat($phrase.lc.comb.grep(/\w/), 'a'..'z').unique[^25].batch(5);
    my %antitable = @table.&deepantipairs(2);

    my $retval;

    for $text.lc.comb.grep(/\w/) -> $char {
        my @deepindex := %antitable{$char};
        $retval ~= @table[||@deepindex];
        @table = @table».List.flat[1..*,0].flat.batch(5);
    }

    $retval
}

say encrypt($phrase, 'As suspicious questions on IRC and Discord revealed, there are quite a few solutions to the PWC that are not made public. One question in particular indicates that there is a build-in in Raku missing.');

# OUTPUT:
# aprdnhbmdrodhvpkdtdxtjpppcuhjkuhmpdvfybpwannjpuychrfvdasojvvadgnwcqcwqkjmndpxexcepjcvjnagvpiopidyalietcknsbseejeqkbopsbpwypbrcuwknsejinlxjsmkxppwdasrrniboewbauejl

Please note the tripple “p” in the cypher. By rotating the replacement table, we mess up statistical properties of the natural language we encrypt. This makes limiting the key-space much harder.

As you likely spotted, I defined a proto with the argument $dimensions (our item may be an Iterable so we can’t infer this argument). Raku has many very handy methods defined in List that work very well with a single dimension. There may be more work to do when we start to support fixed size shaped arrays well.

Categories: Raku

A Subset of Red

April 11, 2022 1 comment

SmokeMachine reported on IRC to have found an unusual use of subset.

class Base {}
class SubClass is Base {}

multi sub trait_mod:<of>(\type, Base $base, |c) { dd type; dd c }

subset Better of SubClass where { put "where: $_"; True };

my Better $a = 42;
# OUTPUT: Better
#         \()
#         where: 42

subset creates a new type with a metaclass of Perl6::Metamodel::SubsetHOW and registers a symbol for it. The of trait is a macro-ish sub that is called at compile time, right after the new type was created. We can get hold of both arguments of that trait and do whatever we want with those type-objects. (There are no further argument, hence the \()).

The where-clause is part of the subset and is called every time we assign a value to a container which is type-checked by the type created with subset. And that is cool. We basically get a callback into container assignment but only for types that are subclasses of a user-defined type. We can even create a fancy runtime error, based on the to be assigned value.

subset Universe of Base where { die 'wrong answer' unless $_ == 42; True }
my Universe $b = 43;
# OUTPUT: wrong answer
            in block  at 2021-03-08.raku line 1513

Red is doing all sorts of fancy meta-programming and I can highly recommend to read its source. With this discovery we got another nice tool in box.

Categories: Raku

Trust issues

March 27, 2022 2 comments

On IRC japhb told us that he needs a class to trust another class that can’t see the to be trusted class.

class B {…}

class A {
    trusts B;
}

class B {

}

This is nice and simple and allows B to call private methods on A. Sadly, that only works if A and B reside in the same file because a forward declaration will cause a compile time error, unless we define the declared type in the same compilation unit.

Method resolution is a runtime creature in Raku. By carefully looking at the code, we can learn where Rakudo stores what we need to cheat with.

class A {
    has $!a = 'private';
    method !a { $!a }
}

multi sub trait_mod:<is>(Mu:U \obj, :$spying!) {
    A.^add_trustee(obj);
}

class B is spying(A) {
    has A $.a = A.new;
    method m { say $!a!A::a }
}

B.new.m;

Luckily, the trait is operates quite early at compile time, so we don’t mess up method dispatch. I’m not feeling to bad poking with a trait where I should not peek. We can always move quickly into NQP-land and break things.

    method cheating {
        use nqp;
        say nqp::getattr($.a, A, 'a');
    }

As shown above, privacy is a matter of politeness. What leaves the question, if forcing a forward declaration to be resolved locally, to be a good design decision. I shall ponder to file a problem solving issue.

Categories: Raku

That meant war

March 7, 2022 1 comment

I try to avoid to allow politics or current events to seep into my blog. The topic is the 100 year language and as such wont be history. Sadly, the head-oligarch has publicly given obvious bullshit-reasons to invade a neighbouring country. I would like to use the powers of to shed some light into the current darkness.

The official numbers (read: favouringly rounded up) for the population of Russia is 145805947 people. If only they could get themselves a decent government, that could have been quite a few hugs. But hugging they ain’t do enough, resulting in a birthrate of 1.82 children per woman. Let’s write that down.

my $woman = 145_805_947 / 2;
my $girls-per-woman := 0.91;

role Degression { }
my \degression = (2022, $woman), -> [$y, $w] { ($y + 35, $w * $girls-per-woman) but Degression } … ∞;
multi sub pretty(Degression [$year, $woman]) {
    "year: $year, Russians in RU: {$woman * 2}"
}

say degression[1000/35].&pretty;
# year: 3002, Russians in RU: 10397626.7
say "Soldiers available in {(1000/35).Int} generations: {Int(degression[1000/35][1]/$woman * 140000)}";
# Soldiers available in 28 generations: 9983

Here, I use an empty role to mark each value in the lazy list. Since I start with a 2-element list, I have to use destructuring and return a list in the generator function. The role allows for multi dispatch and other type checks, as shown in sub pretty.

Russia tries to build up independent industry for more then 10 years. That doesn’t work with a negative population growth. So they utterly depend on immigration from former soviet countries. They also depend on those counties to carry the financial burden to teaching the Russian language. Ukraine wants to join the EU in hopes to repeat the success of the Czechs (Hi Jonathan!) and Polsky (lol, Brexit!) to return to their spiritual home and grow the economy of their ancestors.

If you want to avoid future wars, don’t go to Russia. No people, no soldiers, no war. Also, being paid in ₽ may not be the most solid plan.

Categories: Raku

Self-referring labels

March 3, 2022 Leave a comment

Lizmat kindly allowed Label to expose its file and line-number. That is handy if we want to convey messages about the code itself, without having to worry about edits invalidating our line-numbers. The first use case that came to mind are lightweight singletons that are easy to find.

barcode: Nil;
qrcode: Nil;

say [barcode ~~ qrcode, barcode === qrcode, barcode =:= qrcode]; # [False False False]
put barcode; # barcode ../label-introspection.raku:16

This might be handy when sending messages through a Channel.

my $ch = Channel.new;
start {
    HERE: Nil;
    THERE: Nil;
    $ch.send(.item) for ((1, 2, HERE, THERE) xx ∞).flat;
}

.put for @$ch;
# OUTPUT: 1
#         2
#         HERE ../label-introspection.raku:24
#         THERE ../label-introspection.raku:25
#         …

If those signals end up in a Str unintended, we have a good chance to find the source of the error, even when we have to look at the sender-end of a Channel.

We can also create error messages that point to a different line then a stacktrace might.

sub may-return-nil { }

ENIL: my $var is default(Failure.new(ENIL)) = may-return-nil;
say $var;

EWHOOPSY: fail(EWHOOPSY);

CATCH {
    when X::AdHoc && .payload ~~ Label {
        put "WELP! I encountered {.name} in {.file}:{.line}" with .payload;
    }
}

POD doesn’t allow us to do compile time interpolation (yet). Since it is made up of Arrays, we can cheat.

DOCLABEL: sub described {

}

=begin pod
Sub described is defined in L<PLACEHOLDER>.
=end pod

CHECK $=pod[0].contents[0].contents[1] = DOCLABEL.&{.file ~ ':' ~ .line};

say $=pod;

# OUTPUT: [Pod::Block::Named{:name("pod")}
#             Pod::Block::Para
#             Sub described is defined in
#             ../label-introspection.raku:31
#             .
#         ]

There are quite a few things hidden in CORE and I don’t like to use nqp::attr to get hold of them. A public interface is better then an accidental one. The former make way better idioms.

Categories: Raku