Anonymous slurpers
I have a script where I’m only interested in the last two lines of its output. That’s easy to handle with Shell::Piping
.
px«script.sh ./foo.txt» |» my @a;
my ($second-last-line, $last-line) = @a.tail(2);
That works but is wasteful because it stores lines of text in @a
and keeps them around until the Array
goes out of scope. Also, any temporary variable is a clear indicator of boilerplate. And we don’t do boilerplate in raku.land.
Declarators are quite powerful because they can take a list and immediately hand it over to infix:<=>
. We can even skip values by using anonymous scalars.
my ($, $a, $b) = sub-that-returns-a-list();
I want to do something quite similar. Looking for the last two elements means to skip over any but the last two elements. In subscripts and signatures we use the Whatever *
to indicate multiplicity. (Sometimes I don’t get English. Why is “manyness” not a word? o.O
)
px«script.sh ./foo.txt» |» my (*, $second-last, $last);
That doesn’t quite work because Rakudo doesn’t expect the whatever star in a declarator list. In fact it doesn’t expect any term in that spot. We can work around that by being explicit. While we are on it we may add an anonymous scalar to the mix.
px«script.sh ./foo.txt» |» my (Whatever, $third-last, $, $last);
Declarators return a List
of containers and values. We can use introspection to dissect it.
my \l := my (Whatever, $second-last, $, $last);
say l».&{ .WHAT, .VAR.WHAT, .VAR.name };
# OUTPUT: (((Whatever) Whatever anon) ((Any) Any $second-last) ((Any) Any anon) ((Any) Any $last))
By checking both for type objects and for type object and names in .VAR
we can tell *
, $
and normal containers apart.
sub infix:<|»>(\l, \r) {
sub is-whatever($_ is raw) { (.VAR.name eq 'anon' && .WHAT === Whatever) but role :: { method gist { self ?? '*' !! '' } } }
sub is-anon($_ is raw) { (.VAR.name eq 'anon' && .WHAT === Any) but role :: { method gist { self ?? 'anon' !! '' } } }
sub is-scalar($_ is raw) { (.VAR ~~ Scalar && .WHAT !=== Whatever && .VAR.name ne 'anon') but role :: { method gist { self ?? 'Scalar' !! '' } } }
sub is-left-slurpy(\l) { l.head.&is-whatever but role :: { method gist { self ?? 'left-slurpy: yes' !! 'left-slurpy: no' } } }
sub is-right-slurpy(\l) { l.tail.&is-whatever but role :: { method gist { self ?? 'right-slurpy: yes' !! 'left-slurpy: no' } } }
say r».&{ .&is-whatever, .&is-anon, .&is-scalar }
say r.&is-left-slurpy;
say r.&is-right-slurpy;
}
42 |» my (W, $, $c);
42 |» my ($d, $e, W);
# OUTPUT: ((* ) ( anon ) ( Scalar))
left-slurpy: yes
left-slurpy: no
(( Scalar) ( Scalar) (* ))
left-slurpy: no
right-slurpy: yes
Now I have everything I need to teach Shell::Piping
to skip over values.
That Rakudo doesn’t allow the Whatever star in declarator lists feels like a bug.
my @List = my (42, "", Cool, $, $a);
dd @List;
Array @List = [Mu, Mu, Cool, Any, Any]
It is perfectly fine to skip over literals, take type objects and keep containers but for whatever reason it doesn’t like stars. However, what does work, works really well. It does provide us with containers that we can reason about via .VAR
. Looks like Santa was onto something.
-
January 25, 2021 at 19:342021.04 Grant Reporting – Rakudo Weekly News