Search Results

Keyword: ‘coerce’

Plucking strings

May 17, 2023 Leave a comment

This is a correction and follow-up of my previous post. The ever helpful vrurg provided a simplification to my transformative role. I added some evil to it, mostly for nicer looking introspection.

role Trans[::From, ::To, &c] {
    has To $.value;
    method COERCE(From:D $old) {
        self.new(:value($old.&c))
    }

    unless ::?ROLE.^declares_method(my $mname = To.^name) {
        ::?ROLE.^add_method: $mname, ('my method ' ~ $mname ~ '(--> To) { $.value }').EVAL;
    }
}

By checking if the role contains a method already, I don’t need to fool around with the method table any more. I use .EVAL to compose the method name properly. Rakudo doesn’t care, but a human looking at the method name does not need to be confused. Please note the absence of use MONKEY;. The method form of EVAL doesn’t require it. It is safe to assume code not to be safe.

Task 2 can be written as a naive algorithm. Keep the stickers that contain characters that are also in the target word. Check if all letters in the target word are in the kept stickers. If so, show them or complain.

Again, I need a way to turn words into Sets. I shall do so by invoking the coercion-protocol with a new-method.

class Plucked {
    has $.str;
    has @.chars;

    method new(Str:D $s) { callwith  :str($s), :chars($s.comb) }
    method gist { $!str ~ ' ' ~ @!chars.pairs».&{ .key ~ ' ' ~ .value } }
    method Str { $.str }
    method Set { @.chars.Set }
    method AT-POS($pos) { @.chars.AT-POS($pos) }
    method AT-KEY($key) { @.chars.grep( * eq $key ) }
}

constant PC = Plucked(Str:D);

for [
    ('perl','raku','python'), 'peon';
    ('love','hate','angry'), 'goat';
    ('come','nation','delta'), 'accommodation';
    ('come','country','delta'), 'accommodation'
] -> [ @stickers, PC $word ] {
    my @keep;
    for @stickers -> PC $sticker {
        @keep.push($sticker) if $word ∩ $sticker;
    }
    if ([∪] @keep) ⊇ $word {
        put „"$word" can be made with "$@keep"“;
    } else {
        my $missing = $word ∖ ([∪] @keep);
        put „"$word" can not be made, "$missing" is missing“;
    }
}

The helper class Plucked knows how to dissect Strs and turn them into Sets and Strs. Since laziness is a virtue, I reduce the amount of typing by storing the coercion-type in a constant. Then I write the naive algorithm down.

I didn’t know that I can store coercion-types the same way as other type-objects in a constant. It just DWIMed and that pleases me greatly.

Categories: Raku, Uncategorized

Coercing coercion

May 10, 2023 1 comment

Task 1 of PWC #216 is a Set-operation.

for [('abc', 'abcd', 'bcd'), 'AB1 2CD'; ('job', 'james', 'bjorg'), '007 JB'; ('crack', 'road', 'rac'), 'C7 RA2'] -> [@words, $reg] {
    say @words.grep($reg.lc.comb.grep(/<alpha>/) ⊆ *.comb);
}

The transformation of $reg is a bit unwieldy. I could pull it out and transform it before I use it but then I would have to use is rw. That ain’t neat. What if I could write a type that does the transformation for me? The answer is mildly insane.

role Trans[::From, ::To, &c] {
    has To $.value;
    method COERCE(From:D $old) {
        my \instance = self.new(:value($old.&c));
        my \table = instance.^method_table;
        table{To.^name} = my method ::To(--> To) { $!value }
        instance.^compose;

        instance
    }
}

constant ToSet = Trans[Str, Set, { .lc.comb.grep(/<alpha>/).Set }];

for [('abc', 'abcd', 'bcd'), 'AB1 2CD'; ('job', 'james', 'bjorg'), '007 JB'; ('crack', 'road', 'rac'), 'C7 RA2'] -> [@words, ToSet() $reg] {
    say @words.grep($reg ⊆ *.comb);
}

I create a parametric role that is told how to transform a Str to a Set with a Block and use that as a coercion-type. Things get tricky inside method COERCE because I have to return the role or the coercion-protocol will throw X::Coerce::Impossible. As a result I need to add a method called Set to the parametrised role. Raku doesn’t have the syntax to specify an indirection for method-names (for definitions, calling them can be done with ."$foo"). Hence the use of the MOP. Also, .^add_method doesn’t take a :local-adverb and thus refuses to overload methods provided by Mu and Any. Overwriting the name in the method table is a gnarly hack but works fine — as hacks do.

And so I got myself a way to run code at bind-time in signatures that changes arguments to what I need. I’m not sure what this could be useful for but will keep it in my toolbox nonetheless.

EVAL sadly doesn’t work, because quotes can’t form a closure over a code-object. I believe untangling this would be a nice test for RakuAST-macros and would improve readability for this example quite a bit. In fact, I wouldn’t need a parametric role but could craft a simple class.

Categories: Raku

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

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

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

Assumed predictability

December 27, 2020 2 comments

Vadim does not agree with me. Nor should he. I hardly ever agree with myself. Also, I’m happy for his disagreement because it allows me to write about a topic that I got in the queue for quite some time.

The basic statement is that enforcing types allows reasoning about interfaces at compile time and maybe even earlier — at brain time. A reasonable thing to do in a statically typed language. When objects are involved, Raku does plenty of its thinking at runtime. Let’s have a look at two examples.

class Foo {
    submethod shift-right($i) { }
}

sub foo(Foo() $handle) {
    $handle.shift-right(4);
}

foo(Foo.new);

class Bar is Foo {
}

foo(Bar.new);

# OUTPUT: No such method 'shift-right' for invocant of type 'Bar'

We ask the compiler for a type check against Foo that Bar satisfied. Then we proceed to call a method on an instance of Bar that is only supplied by Foo. The compiler tried to help us but couldn’t. In this example our interface is not Foo and its methods but only Foo — excluding its child classes — and its methods. This is a subtle difference that will hurt us at runtime. With another example I can illustrate quite clearly why.

class Catchall {
    has $.the-object;
    method FALLBACK($name, |c) {
        self.the-object."$name"(|c)
    }
}

sub foo(Catchall $c) {
    $c.any-name(42);
}

say foo( Catchall.new: the-object =>
    class :: {
        method any-name($i) { 42 ~~ $i ?? 'Universe' !! 'wut‽'  }
    }
);
# OUTPUT: Universe

Can you name the interface that is being used by sub foo? That’s a mean question of course, as the class that supplies the interface got no name. The type constraint provides the interface of an accessor method to $.the-object and anything inherited from Any. The latter might change with new language versions btw. Consequently, the real interface is more like *.any-name() with a type constraint of Any. Those two are simple examples. Any module you use might use MONKEY-TYPING or fiddle with the MOP. Runtime interfaces are utterly unpredictable in Raku and we all do well to use Test.

That being said, Vadim is right to uphold the principle of least surprise. We start the names of roles with a capital to indicate it to be a type object and thus the wish to honour some sort of interface. I would be happy for a more general solution to the problem of “slurpy” coercion. Technically, Raku got the syntax for it already.

sub Filish(Any:D $handle where * ~~ Str|IO::Handle|IO::Path --> IO::Handle) {
    # ... coerce away here
}

sub foo(&Filish() $handle) {
    $handle.put: "This would make me happy!";
}

This form would basically say: “I want the coercion handled by a sub called Filish“. It would allow the code in Filish to be reused and as such provide flexibility, without giving the impression to promise interface. At least in this example the signature of the coercion sub contains its own documentation. There may even be room for some compile time checks, so long as we don’t use a multi. The parameter $handle must satisfy the signature of Filish. Having a sub would allow a module user to .wrap it.

Being in general disagreement with myself can be challenging but does allow me to change my mind easily. Since this is likely my last blog post this year I wish you all exclusively nice reasons to change your minds in 2021.

Categories: Raku

Coercive files

December 25, 2020 2 comments

Many APIs have a routine or two that take a file as an argument. In Raku we can easily turn Str into IO::Path and subsequently into IO::Handle. As a module author it’s a polite move to provide MMD variants, so the user can supply what they like.

sub s($file is copy where { $_ ~~ Str|IO::Path|IO::Handle or fail(&?ROUTINE.name ~ ' likes Str, IO::Path and IO::Handle.') } ) {
    given $file {
        when Str { $file = .IO.open; }
        when IO::Path { $file = .open; }
        when IO::Handle { }
    }

    say ?$file;
}

This is boilerplate. Which in the kingdom of Raku is almost banned. Using the new coercion protocol, we can implement a role to happily save time ever after.

role Filish[*%mode] is IO::Handle {
    proto method COERCE($) {*}
    multi method COERCE(Str:D $s) {
        my $handle = self.new: :path($s.IO);
        $handle.open: |%mode
    }
    multi method COERCE(IO::Path:D $p) {
        my $handle = self.new: :path($p);
        $handle.open: |%mode
    }
    multi method COERCE(IO::Handle:D $h) {
        $h
    }
}

sub f(Filish[:w, :a, :!bin]() $handle) {
    $handle.put: "foo" xx 42;
    $handle.close;
}

f('/tmp/foo.txt');

With the coercing type constraint Filish[:w, :a, :!bin]() we basically say: “Give me something that represents a file, that I will open for writing in the appending fashion.”. I was not aware of the possibility to use a slurpy in the parameter list of a role (*@a works too). This seems to be an ENODOC. Since it makes my file easier I wont complain.

The new coercion protocol is very useful but got one flaw. It forces me to return the type that contains the COERCE-method. In a role that doesn’t make much sense and it forces me to juggle with IO::Handle. It took me 30 minutes to figure out how to successfully subclass it. There may be classes in modules that are even worse. Some programmers really like their private attributes. It would be nice to drop that restrictions on roles and/or if a return type is explicitly provided. With the current model, something easy is made hard.

Anyway, I got it working and might stick a module into the ecosystem, once I came up with a good name.

Categories: Raku

Guarding dynamics

August 14, 2020 1 comment

Dynamic variables are a nice way to get the benefits of global variables without taking the drawbacks. They pass information up the call tree without forcing a declaration upon the caller. However, dynvars share a burden with exceptions. The callee knows how to do what the caller might not expect.

use Module::By::Author::A;
use Module::By::Author::B;

my $*dynvar = 42;

sub-from-A(); # expects $*dynvar to be Int
sub-from-B(); # expects $*dynvar to be IO(Str)
sub-from-C(); # expects $*dynvar to be IO::Handle

In this example sub-from-B() will silently fail until it tries to open the file named “42”. While sub-from-C() will try to coerce 42 to become a file handle and throw. So there lies a danger in dynvars expected to be set by independent modules. Worse, the behaviour might suddenly pop up after any zef --install. Raku is a dynamic language that will try its best to convert values automatically and fail at runtime. It is good advice to support the compiler by failing early.

I want to use dynvars in a few modules to provide semi-global flags to remove boilerplate. The following seems to provide protection against unintentional dynvar-spill over.

class Switch is Mu {
    has $.name;
    method gist { $.name }
    method Str { die('invalid coersion') }
}

constant on = Switch.new: :name<on>;
constant off = Switch.new: :name<off>;

sub s() {
    # put $*d; # this will die
    say $*d;
    dd $*d;
}
my $*d = off;
s();
# OUTPUT: off
#         Switch $*d = Switch.new(name => "off")

I derive from Mu to get rid of all the coercers from Cool and overload Str to take that one out of the loop as well. Using say for anything but debugging is a bug anyway, so I might support it properly with .gist. Since I create a type with the class I can then go and protect my subs and methods with a whereception.

sub dyn-var-typecheck(Mu:U \T, $name) {
    sub {
        DYNAMIC::($name) ~~ T || die(„$name is set to an unexpected value“)
    }
}

constant &dyn-s-typecheck = dyn-var-typecheck(Switch, ‚$*d‘);

sub s($? where dyn-s-typecheck) { }
# OUTPUT: $*d is set to an unexpected value
#         in sub  at /home/dex/projects/blog/shielded-dynvars.raku line 6
#         in sub s at /home/dex/projects/blog/shielded-dynvars.raku line 22
#         in block <unit> at /home/dex/projects/blog/shielded-dynvars.raku line 29

Using the DYNAMIC::($name) in a where clause will slow down any call on MMD. So pulling the check into the sub might be reasonable.

With this measure I feel better to add $*always-capture-stderr to Shell::Piping to get rid of :stderr(Capture) on pretty much any pipe I start. And I feel a lot better when adding $*debug in everywhere.

Raku was not designed with type checks on dynvars in mind. It was designed to be a Perl. That gives us the flexibility to fix problems as they occur.

Categories: Raku

Handling Failure

July 27, 2020 1 comment

After some back and forth I have found a practical way to handle error conditions in Shell::Piping. The practical way is to have more then one way. A process does have an exitinteger (called a code, because it can be quite cryptic) and text output to STDERR to indicate something went wrong. Sometimes we need sloppy error handling, sometimes we need to look into the textual output and react to it.

I found a really nice way to use a Junction and Rakus type system to remove some boilerplate from error handling. Combining both allows us to create a flexible type.

class Exitcode {
    has $.value;
    has $.command;
    method Numeric { $.value }
    method Str { $.command }
}

So this class produces objects that are both a number and a text. What is actually looked at depends on who is looking. We can use infix:<~~> to make the decision which comparison operator to use.

say $ex ~~ 42 && $ex ~~ ‚find‘; # OUTPUT: True

That’s still quite wordy. We can use a Junction because it binds tighter then ~~.

say $ex ~~ 42 & ‚find‘; # OUTPUT: True

Now we can CATCH an Exception and narrow done the command in a pipe that failed easily.

CATCH {
    when X::Shell::NonZeroExitcode {
        given .exitcode {
            when 42 & ‚find‘ {
                warn ‚Oh now! We found the answer!‘;
            }
        }
    }
}

Not all users of a module might like to use Exceptions. So we use a construct in a Shell::Pipe object to create a Failure to return from .sink. If the method Shell::Pipe.exitcode is called, we assume the user is dealing with them by hand. We can then call .handled to “abort” the Exception. This has to be easy or it might get skipped. Hence the unusual usage of the coercer methods in the class Exitcode.

Categories: Raku