Archive

Archive for August, 2021

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

Inequality

August 13, 2021 2 comments

As stated before, I like to read code I didn’t write myself. Flavio had trouble with triples. One line stood out to me.

take @triple if $N == @triple.any;

This looks like a set-operation to me. But using $N ∈ @triple dropped one result. After some debugging I found the culprit.

.map({($_, $n / $_)});  # take it and its counterpart

This might look like a division but is actually a type cast to Rat.

say 1 ∈ (1/1, );
# OUTPUT: False

Rakudo implements set-operations as equivalence checks, not numerical equality. Quite in contrast to ==, eqv does a type check and Int aint’t Rat. This might explain that the mathematical inclined don’t use Set as much as I did expect them to. The type mismatch simply produces a result that is not useful to mathematicians.

As implemented right now, we can’t tell the set operators what we consider equality . With meta-operators and junctions, we can specify what operator we actually want to use. The set-operators are not meta-operators and at least for now, we can’t user define new meta-operators. However, we can lexically redefine ordinary operators.

proto sub infix:<(elem)>($, $, *% --> Bool:D) is pure {*}
multi sub infix:<(elem)>(Numeric:D \a, Iterable:D \listy --> Bool:D) {
    Any.fail-iterator-cannot-be-lazy('∈', '') if listy.is-lazy;

    for listy -> \b {
        return True if a == b;
    }

    False
}

constant &infix:<∈> := &infix:<(elem)>;

proto sub infix:<(&)>(|) is pure {*}
multi sub infix:<(&)>(Iterable:D \lhs, Iterable:D \rhs) {
    Any.fail-iterator-cannot-be-lazy('∩', '') if lhs.is-lazy || rhs.is-lazy;

    my @result;

    for lhs -> \l {
        for rhs -> \r {
            @result.push: r if l == r;
        }
    }

    +@result ?? @result !! ∅
}

constant &infix:<∩> := &infix:<(&)>;

say 1 ∈ (1/1, );
say (42, 42/2, 42/3) ∩ (1, 21, 3);
# OUTPUT: True
#         Set(21)

The proto is needed, because we need to get rid of multi-candidates that are already present. A more general approach might be to have a &*SET-COMPARATOR that defaults to &infix:<cmp>. This would slow down a fairly simple operator by a factor of 13. I may still be able to write a module that takes a comparator and returns a host of operators. With proper macros this would be easy. Maybe next year.

For now I shall be evil and report back with success.

Categories: Raku

They returned an empty package

August 7, 2021 1 comment

I don’t like to solve maths-puzzles. I do like to read other folks solutions thought. You never know where to spot a new idiom. A good way to find them is to look for code that feels unusual.

method normalize (Numeric:D $sum = 1) {
  my $total = self.total or return;
  my $factor = $sum / $total;
  %!pmf.values »*=» $factor;
  self;
}

I have never seen the construct in the first line, at least not in a method. The return is triggered when self.total returns something falsesy, like 0. It protects the 2nd line from a division by zero by returning Nil. Let’s see if that actually works.

$cookie.multiply('Bowl 1', 0);
$cookie.multiply('Bowl 2', 0);
say 'probability it came from Bowl 1: ', $cookie.P('Bowl 1');

# OUTPUT: Attempt to divide by zero when coercing Rational to Str
            in sub MAIN at tbr-pmf.rakumod line 73
            in block <unit> at tbr-pmf.rakumod line 3

Well, it does blow up some place else. This is not an unreasonable scenario either. When I’m around, the likelihood of a cookie to come from bowl1 or bowl2 is indeed 0. Returning to normalize we can check what happens when a method that should return self returns Nil.

class C {
    method foo { Nil }
}

dd C.new.foo.bar;
# OUTPUT: Nil

The cause of this mis-dispatch can be found in src/core.c/Nil.pm6:16:

method FALLBACK(| --> Nil) { }

Nil is the baseclass of Failure and as such will only throw when assigned to or used to gain values from a list. It’s purpose is to revert containers to their default value. In a numeric context it will warn and turn into 0. Eventually it might end up in a division and cause much grief. Dealing with a depressing lack of cookies could be done with a multi-method.

   multi method P ($key where { self.total == 0 } ) {
        0
   }
   multi method P ($key) {
      die "no key '$key' in PMF" unless %!pmf{$key}:exists;
      return %!pmf{$key} / self.total;
   }

This works because self is an implicit part of the signature of a method (sans an explicit invocant).

Another thing I never seen before is the following gist method.

method gist () {
  return gather {
    take '---';
    for %!pmf.keys.sort -> $key {
      take "  «$key» {%!pmf{$key}}";
    }
  }.join("\n");
}

Using take to prefix the resulting list is quite neat. Using a gather-block avoids any nesting that might need flattening afterwards. In a gist-method it’s a bit wasteful though. We like to truncate lists after 100 elements in say. This could be done with a .head(100) after .keys. Or we skip the rather slow gather-block altogether (each take fires a control exception and Rakudo can not optimise those away yet).

method gist () {
  ( |'---', |%!pmf.keys.sort.map: { "  «$_» %!pmf{$_}" } ).head(100).join($?NL)
}

I like to avoid explicit flattening by using Slips. If the map-block gets complicated, this allows to sneak a .hyper in to gain some speed on large lists.

Please keep in mind that cookies are nice and Nil is shifty.

Categories: Raku

Only infinite elements

August 2, 2021 2 comments

Flavio solved a puzzle with an implementation that puzzled me. The following code appears not as idiomatic as such a simple problem should require.

sub simulation-round () {
   return [+] gather {
      loop {
         my $value = roll-die();
         take $value;
         last if $value < 3;
      }
   }
}

We tend to do our dice rolls with a simpler construct.

sub simulation-round () {
    [+] (1..6).roll(*).map({ last .Int if .Int < 3; .Int });
}

# OUTPUT: Cannot .sum a lazy list onto a Seq
#           in sub simulation-round at ETOOBUSY-2.raku line 4
#           in block <unit> at ETOOBUSY-2.raku line 7

Rakudo assumes that a lazy list must be infinite. This would catch many bugs but is not what I want in this case. Sadly we don’t have a build-in to say .lazy-but-finite. Neither do we got roles we could mixin. This might be the reason why it took me 10 minutes of staring‑at‑the‑code to find a solution.

[+] (1..6).roll(*).map({ last if .Int < 3; .Int })[^∞]

So we are asking for up to infinite elements of this infinite list to defuse the laziness. This smells of bad design and may warrant a problem solving issue.

The rest of my version is quite different because I wanted to shoehorn a .hyper in.

use v6.*;

unit sub MAIN($rounds = 1_000_000);

sub term:<🎲>() { (1..6).roll(*) };

sub simulation-round () {
    [+] 🎲.map({ last .Int if .Int < 3; .Int })[^∞];
}

my $total = (0..^$rounds).hyper(:degree(12), :batch(10000)).map({ simulation-round }).sum;

put ‚average gain: ‘, $total / $rounds;

I believe there is a life‑lesson to be learned. Laziness can be problematic if the compiler is less then virtuos.

UPDATE:

As lizmat pointed out, last .Int requires v6.e.PREVIEW (v6.* will work too). Instead of [^∞] a simple .eager will do (but will break the title of the blog post). We can also use ^$rounds instead of (0..^$rounds) if we disambiguate with a space before the method call.

my $total = ^$rounds .hyper(:degree(12), :batch(10000)).map({ simulation-round }).sum;

Categories: Raku