Archive

Author Archive

Symmetric code

November 29, 2021 Leave a comment

While reading Arne`s solution for Challenge #140.2, I spotted a nested simple loop.

for 1 .. $i -> $ii                                               # [3]
{
  for 1 .. $j -> $jj                                             # [3]
  {
    @result.push: $ii * $jj;                                     # [4]
  }
}

In Raku-land, we don’t really need simple loops. Often we can .map or use a hyper operator. I had the nagging feeling this is also the case for simple nested loop. A good night’s sleep later I remembered that “X” marks the spot.

The cross-product-operator X is implemented with 2 nesting loops and returns a list of lists. In Raku we construct lists with infix:<,>.

use Test;
is-deeply ((1,2) X, (3,4)), ((1,2) X (3,4)), 'yep!';

So we are actually calling X,. If we replace infix:<,> with infix:<*>, we get what Arne did without the temporary container.

for [2, 3, 4; 3, 3, 6; 200, 300, 40; 0, 0, 0] -> [$i, $j, $k] {
    put ((1..$i) X* (1..$j)).sort[$k - 1];

    CATCH { when X::OutOfRange { warn „Sorry, I can't find position $k in the multiplication table of $i and $j.“ } }
}

I wonder if there are more cases, where we could look at the implementation of a high-level language feature, to spot places worthy of replacing wordy code with a single operator call.

Categories: Raku

Leaky Rakudo

November 28, 2021 Leave a comment

Yesterday the discord-bridge-bot refused to perform its 2nd job: EVAL All The Things! The EVALing is done via shell-out and requires a fair bit of RAM (Rakudo is equally slim then Santa). After about 3 weeks the fairly simple bot had grown from about halve a GB to one and a halve – while mostly waiting for the intertubes to deliver small pieces of text. I complained on IRC and was advised to take heap snapshots. Since I didn’t know how to make heaps of snapshots, I had to take timo’s directions towards use Telemetry. As snap(:heap) wasn’t willing to surrender the filename of the snapshot (I want to compress the file, it is going to get big over time) I had a look at the source. I also requested a change to Rakudo so I don’t have to hunt down the filename, which was fulfilled by lizmat 22 minutes later. Since you may not have a very recent Rakudo, the following snipped might be useful.

    multi sub postfix:<minute>(Numeric() \seconds) { seconds * 60 }
    multi sub postfix:<minutes>(Numeric() \seconds) { seconds * 60 }
    multi sub postfix:<hour>(Numeric() \seconds) { seconds * 3600 }
    multi sub postfix:<hours>(Numeric() \seconds) { seconds * 3600 }
    multi sub postfix:<day>(Numeric() \seconds) { seconds * 86400 }
    multi sub postfix:<days>(Numeric() \seconds) { seconds * 86400 }

    start react whenever Supply.interval(1day) {
        note ‚taking snapshot‘;
        use Perl6::Compiler:from<NQP>;
        sub compress(Str() $file-name) { run «lz4 -BD --rm -qf $file-name» }

        my $filename = 'raku-bot-' ~ now.DateTime.&{ .yyyy-mm-dd ~ '-' ~ .hh-mm-ss } ~ '.mvmheap';
        Perl6::Compiler.profiler-snapshot(kind => "heap", filename => $filename<>);
        $filename.&compress or warn(‚compression failed‘);

        note ‚done‘;
    }

If you got a long running MoarVM process, please consider to check if it slowly fills all RAM and provide the snapshots to #rakudev.

Categories: Raku

2nd class join

November 8, 2021 2 comments

For challenge #137.1 we are looking for long years. We can implement the algorithm as described in Wikipedia (and ignore that Dateish got .week-number to have a reason for showing off with junctions).

multi sub infix:«|,»(\left, \right) is equiv(&infix:<Z>) { |left, |right }

say (1900..2100).grep({ Date.new(.Int, 1, 1).day-of-week | Date.new(.Int, 12, 31).day-of-week == 4 });

# OUTPUT: 1903 1908 1914 1920 1925 1931 1936 1942 1948 1953 1959 1964 1970 1976 1981 1987 1992 1998 2004 2009 2015 2020 2026 2032 2037 2043 2048 2054 2060 2065 2071 2076 2082 2088 2093 2099

The output doesn’t look too nice. It would be better to group the years in column.

put ( (1900..2100).grep({ Date.new(.Int, 1, 1).day-of-week | Date.new(.Int, 12, 31).day-of-week == 4 }) Z (' ' xx 7 |, $?NL) |xx ∞ ).flat.head(*-1).join('');

# OUTPUT: 1903 1908 1914 1920 1925 1931 1936 1942
          1948 1953 1959 1964 1970 1976 1981 1987
          1992 1998 2004 2009 2015 2020 2026 2032
          2037 2043 2048 2054 2060 2065 2071 2076
          2082 2088 2093 2099

That is pretty long and convoluted. The reason why I need Z with an infinite list and have to remove the last redundant element (either a newline or space) is that .join isn’t all that smart. Let’s build a smarter function.

multi sub smart-join(Seq:D \separator, *@l --> Str:D) {
    my $ret;
    my $sep-it = separator.iterator;
    my $list-it = @l.iterator;

    loop {
        my $e := $list-it.pull-one;
        last if $e =:= IterationEnd;

        $ret ~= $e;
        $ret ~= $sep-it.pull-one if $list-it.bool-only;
    }

    $ret
}

multi sub infix:«|xx»(Mu \left, Mu \right) is equiv(&infix:<xx>) { (left xx right).flat }

1900..2100
==> grep({ Date.new(.Int, 1, 1).day-of-week | Date.new(.Int, 12, 31).day-of-week == 4 })
==> smart-join( (' ' xx 7 |, $?NL) |xx ∞ )
==> say();

Now we can use (the sadly under-used) feed operator. The lazy list that is generating the alternating separators might be a bit slow. If we go functional the code, both for smart-join and the alternator, gets simpler.

multi sub smart-join(&separators, *@l --> Str:D) {
    my $ret;

    while @l {
        $ret ~= @l.shift;
        $ret ~= separators if +@l;
    }

    $ret
}

1900..2100
==> grep({ Date.new(.Int, 1, 1).day-of-week | Date.new(.Int, 12, 31).day-of-week == 4 })
==> smart-join({ ++$ %% 8 ?? $?NL !! ' '})
==> say();

Right now there is no easy way to add this as a module because sub join is not a multi. Since we use Z with such infinite lists quite often, I believe a proper functional way in CORE could not hurt. Raku does support functional programming in many places but some subs are still 2nd class citizen. It may be reasonable to make a list so I can hand it over to Santa.

UPDATE:

As lizmat noted, sub join is a multi already. So there is room for a module.

Categories: Raku

Should it mutate or not? YES!

November 5, 2021 1 comment

On Discord Hydrazer was looking for a list concatenation operator. That leaves the question if it should mutate like Array.push or return a new list of Slips.

sub infix:«|<<»(\a, \e) {
    Proxy.new(FETCH => method { |a, |e },
              STORE => method (\e) {})
              but role { method sink { a.push: e } };
}

my @a = 1,2,3;
@a |<< 4;
dd @a;
my @b = @a |<< 5;
dd @a, @b;

In sink-context returning a new list would not make sense. With a Proxy that provides a sink-method we can answer the question with “YES!”.

This made me wonder if Proxy should have the optional argument SINK. Right now there is no way to define containers in pure Raku. Even with subclassing we would have to decent into nqp-land. As newdisp has shown, it tends to be quite brittle to make modules depend on use nqp;`.

Categories: Raku

TIMTOWTDItime

November 5, 2021 1 comment

On Discord flirora wished for a way to merge list elements conditional. In this instance the condition is that any element that starts with a space is part of a group.

{
    my @a = ("apple", " banana", " peach", "blueberry", "pear", " plum", "kiwi");

    multi sub merge-spacy([]) { () }
    multi sub merge-spacy([$x is copy, *@xs]) {
        if @xs[0].?starts-with(' ') {
            $x ~= @xs.shift;
            merge-spacy([|$x, |@xs])
        } else {
            $x, |merge-spacy(@xs)
        }
    }

    dd merge-spacy(@a);
}
# OUTPUT: ("apple banana peach", "blueberry", "pear plum", "kiwi")

This functional version is neat but slow. Rakudo can’t inline recursion and doesn’t do any other optimisations yet.

my @a = ("apple", " banana", " peach", "blueberry", "pear", " plum", "kiwi");

sub merge-with(@a, &c) {
    gather while @a.shift -> $e {
        if @a && &c(@a.head) {
            @a.unshift($e ~ @a.shift)
        } else {
            take $e;
        }
    }
}

dd @a.&merge-with(*.starts-with(' '));

# OUTPUT: ("apple banana peach", "blueberry", "pear plum", "kiwi").Seq

With gather/take we don’t have to worry about recursion and the returned Seq is lazy be default. This can provide a big win if the list gets big and is not wholly consumed.

my @a = ("apple", " banana", " peach", "blueberry", "pear", " plum", "kiwi");

multi sub join(*@a, :&if!) {
    class :: does Iterable {
        method iterator {
            class :: does Iterator {
                has @.a;
                has &.if;
                method pull-one {
                    return IterationEnd unless @!a;

                    my $e = @!a.shift;
                    return $e unless @!a;

                    while &.if.(@!a.head) {
                        $e ~= @!a.shift;
                    }

                    return $e;
                }
            }.new(a => @a, if => &if)
        }
    }.new
}

.say for join(@a, if => *.starts-with(' '));

This version should please lizmat as it uses iterators. The conditional is also factored out and CORE will use the Iterator lazily wherever possible. In production code I would get rid of the return-statements and replace them with ternary operators to get a little extra performance.

The original question (that clearly got me carried away) asked for the groups to be join. Once we lost a structure it can be difficult to reconstruct it.

my @a = ("apple", " banana", " peach", "blueberry", "pear", " plum", "kiwi");

#| &c decides if the group is finished
sub group-list(@a, &c) {
   my @group;
   gather while @a {
       my $e = @a.shift;
       my $next := +@a ?? @a.head !! Nil;
       @group.push($e);
       if !c($e, $next) {
           take @group.clone;
           @group = ();
       }
   }
}

dd @a.&group-list(-> $left, $right { $right && $right.starts-with(' ')});

Here the conditional gets two elements to decide if they belong to the same group. It is also the first time I used .clone.

Thanks to a simple question I learned quite a bit. It forced me to think about the disadvantages of my first idea. Maybe code challenges should explicitly asked for more then one answer for the same question.

Categories: Raku

Double inspiration

October 9, 2021 2 comments

Quite a few of the posts prior to this one where inspired by a post of fellow blogger. I would like to double down on that today. Vadim wrangled with symbols and Fabio enjoyed renaming them. Having struggled with packages in the past, Vadim’s post was very helpful in making me realise, .HOW is how I can get hold of the object that is the package. And if Perl can do it, there is surely no way to stop Raku to have the same capability.

We want to re-export functions while changing their name. Just adding a prefix will do for now. That presents the first problem. Currently, there is no way to get named arguments to use handed to sub EXPORT. Any Hash will also be gobbled up. All we have are positional parameters. Since Raku is omni-paradigmatic, that wont pose a challenge.

use Transport {'WWW', :prefix<www->};

We can execute that block and use destructuring to get hold of the positional and any colonpair.

use v6.d;

sub EXPORT(&args) {
    my ($module-name, *%tags) = args;

    my \module = (require ::($module-name));
    my %exports = module.WHO<EXPORT>.WHO<DEFAULT>.WHO.&{.keys Z=> .values};
    my %prefixed-exports = %exports.map: { .key.substr(0, 1) ~ %tags<prefix> ~ .key.substr(1..*) => .value };

    %prefixed-exports.Map
}

The only trouble I had was with .WHO being a macro and not a method of Mu. So we need a symbol to hold the package, which is returned by require.

dd &www-jpost;
# OUTPUT: Sub jpost = sub jpost (|c) { #`(Sub|94403524172704) ... }

I didn’t turn this into a proper module, yet. This needs more reading (what the Sub::Import is actually being used for) and thinking. A 1:1 translation from Perl seems to be the easy way and thus is likely not the most correct.

Categories: Raku

Convolution

September 24, 2021 1 comment

Flavio wrote a straightforward solution to PWC-131-1 and wondered if there is a idiomatic way. Assuming, that “idiomatic” means to use language features which lesser languages refuse to require, I’m happy to deliver convoluted code.

use v6.d;

sub consecutive-a(*@a) {
    my @ret;

    gather {
        for (|@a, |@a.tail).rotor( 2 => -1 ) -> [$a, $b] {
            @ret.push: $a;
            unless $b == $a + 1 {
                take @ret;
                @ret = [];
            }
        }
    }
}

sub consecutive-b(*@a) {
    my @gaps = @a.rotor(2 => -1).kv.grep(-> $index, [$a, $b] { $b !== $a + 1 })[*;0];
    return @a unless @gaps;
    @gaps = (@gaps Z @gaps »+» 1).flat;
    my $ranges := (0, @gaps, (@a - 1)).flat.map(-> \l, \r { l .. r });

    @a[$ranges]
}

sub MAIN() {
    my @examples := (1, 2, 3, 6, 7, 8, 9)
                  ,(11, 12, 14, 17, 18, 19)
                  ,(2, 4, 6, 8)
                  ,(1, 2, 3, 4, 5);

    .&consecutive-a.say for @examples;
    say();
    .&consecutive-b.say for @examples;
}

Both exibits use .rotor to create easily comparable pairs of numbers. The first variant uses gather/take to return the by PWC requested sublists lazily. If we spot a gap take the list and empty the Array-container. If numbers are consecutive we add them to the return buffer. The laziness may help with very large lists.

The 2nd version creates a list of gaps. Since we can’t point in-between two elements, we first take the last index of the last element of a sub-list and then zip the following element, hoping the Array not to contain holes. The first and last index of @a are added. We now have a list of begin- and end-indices of the sub-lists. Those are composes to Range-objects. Such a list (Rakudo doesn’t like an Array in this case) can be used in Positional-subscripts to gain the desired sub-list of consecutive Ints. This solution can take a shortcut if no gaps are found.

I’m not entirely sure if this is better then a boring way to solve the task. It does provide a reason for another blog post, though. Quite helpful to reach 150 posts before the end of the year.

Categories: Raku

Calling by name

September 3, 2021 1 comment

While looking for something completely different, I found that Roast really likes named callable placeholder variables.

dex@dexhome:~/projects/raku/roast$ ack -l  '&\:'
S02-names/SETTING-6c.t
S02-names/SETTING-6e.t
S02-names/pseudo-6c.t
S02-names/pseudo-6d.t
S02-names/pseudo-6e.t
S02-names/symbolic-deref.t
S06-operator-overloading/infix.t
S06-signature/closure-parameters.t
S32-num/rounders.t
integration/advent2013-day10.t
integration/weird-errors.t

That is hardly a surprise, given that the specs are composed of short snippets of code. Placeholder variables fit right in!

sub foo { "Good &:greeting(now.DateTime.hour) $:name!" };
say foo :name<Paul>, :greeting{$_ < 12 ?? 'morning' !! 'day'};
say &foo.signature;

# OUTPUT: Good day Paul!
          (:&greeting!, :$name!)

It’s really nice how interpolation and placeholders work together here. I have reported the ENODOC.

Even after years and years of being a Raku beginner, I still find new stuff when digging deep enough. Maybe it’s a good thing that we don’t have a Raku book. It would surely be backbreaking.

Categories: Raku

Dynamic declaration

August 24, 2021 1 comment

Shortly after my last blog post, Stashes raised a question. Coincidence? Conspiracy? You decide! Anyway, the EVAL caught my eye, because with it we can dynamically create compile time constructs such as a package.

our package EXPORTHOW {
}

sub EXPORT($declarator-name = 'registered') {
    use MONKEY-SEE-NO-EVAL;
    OUR::EXPORTHOW::DECLARE := EVAL q:s:to /EOH/;
        package DECLARE {
            constant $declarator-name = MetamodelX::RegisteredHOW;
        }
        EOH

    Map.new
}

Thanks to be our-scoped, the package EXPORTHOW can be modified at runtime. The EVAL allows to define a constant at runtime. Runtime in this context, is when the use statement of a consuming module is executed.

use Registered 'remembered', :recall-types;

remembered Foo {
    method answer { 42 }
    method common { self.^name }
}

With this technique, we allow the user of a module to decide what symbols are being used for a declarator. Pretty handy, if a module is added late to a project, which might have occupied a given symbol already. Quite some time ago, I lamented a little lizmats decision to modify class with InterceptAllMethods. This is now a solvable problem.

I once believed Raku to be less dynamic then Perl. Looks like I have to reconsider.

Categories: Raku

Most fancy

August 17, 2021 Leave a comment

On Discord (yes, we are that cool) MrDispatch wished for a way to collect a bunch of classes right after their declaration. I believe, with the power of the MOP, we can go a step further and register the type-object even before the definition is finished.

class MetamodelX::RegisteredHOW is Metamodel::ClassHOW {
    our @registered;

    method new_type(|) {
        my \type = callsame;
        type.HOW.remember_type(type);

        type
    }

    method remember_type(Mu \type) {
        @registered.push: type
    }

    method recall-types {
        @registered;
    }
}

sub recall-types is export(:recall-types) {
    MetamodelX::RegisteredHOW.recall-types;
}

my package EXPORTHOW {
    package DECLARE {
        constant registered = MetamodelX::RegisteredHOW;
    }
}

We introduce a new declarator registered that hooks up a new meta-class. It would be possible to overload class to register all classes in a compilation unit. For now, doing so would not be good conduct, because playing with EXPORTHOW is not quite lexical yet. The list of declared type objects will reside as a class-attribute inside the meta-class. We can access it through any type object or instance via .HOW or with the exported sub recall-types.

use v6.*;

use Registered :recall-types;

registered Foo {
    method answer { 42 }
    method common { self.^name }
}

registered Bar {
    method ohai { ‚Hello Universe!‘ }
    method common { self.^name }
}

Foo.HOW.recall-types.say;
say Foo.new.HOW.recall-types;
say recall-types; # requires the import adverb :recall-types;
say recall-types».common;

# OUTPUT: [(Foo) (Bar)]
#         [(Foo) (Bar)]
#         [(Foo) (Bar)]
#         [Foo Bar]

There are plenty of other ways to implement a registry of types. Using a custom meta-class is the most fancy and flexible way. We could hook into many other things here too. Rakudo is a dynamic compiler for a dynamic language. It uses introspection much more then you do. Sometimes it introspects a package called EXPORTHOW for a key called DECLARE to create a keyword that is more classy then class.

Categories: Raku