Why is it that sometimes we have to use coercion to access individual elements of an array? - raku

My understanding is that in the code below I wouldn't have to use (2), just (1) would suffice to print each element of the array in its own line.
$ cat test-hash.raku
use v6;
my %h;
say "";
say "inserts a scalar";
%h.push: (aaa => "aaa");
%h.say;
say "";
say "turns the scalar into an array";
%h.push: (aaa => "AAA");
%h.say;
say "";
say "(1) shows all elements of the array in a line";
.say for %h{"aaa"};
say "";
say "(2) shows each element of the array in a line";
.say for %h{"aaa"}.Array;
$ raku test-hash.raku
inserts a scalar
{aaa => aaa}
turns the scalar into an array
{aaa => [aaa AAA]}
(1) shows all elements of the array in a line
[aaa AAA]
(2) shows each element of the array in a line
aaa
AAA
$

You could argue that this is a bug in the implementation of Array.push, because it assigns an Array to the Hash element, rather than binding to it. Because it assigns, the Array lives in a Scalar container, and is effectively itemized. And being itemized, it won't be iterated over.
So, the easiest solution is to de-itemize the Hash elements before iterating:
.say for %h{"aaa"}<>;
See decontainerization operator for more info.

Related

How on Raku do we fill out multi variables individually from corresponding regex captures

How is the simplest Raku to fill out so many individual variables from a result regex captures in corresponding order
( just like Perl my ($a, $b $c, $d, $e)= 'hello' =~ m{ ^(h) (e) (l) (l) (o) }x
) ?
try such:
> my ($a, $b, $c, $arr);
[<$a $b $c>]= 'hello world' ~~ m{ (h)(e)(l)lo };
say "\n=$a\n=$b\n=$c"
Use of uninitialized value element of type Any in string context.
Methods .^name, .raku, .gist, or .say can be used to stringify it to something meaningful.
in block <unit> at <unknown file> line 1
...
fails.
Please show the correct and nearest similar way to Perl.
#my ($a, $b, $c, $d, $e) = 'hello' =~ m{ ^(h) (e) (l) (l) (o) }x; # Perl
my ($a, $b, $c, $d, $e) = 'hello' ~~ m{ ^(h) (e) (l) (l) (o) } .list; # Raku
Explaining the differences:
{ ... }x In Perl, white space in a regex is part of the pattern unless you append an x adverb/option. Not so in Raku.
.list. In Perl, matching operations generally return either one string, or a list of strings. It's the same in Raku, except:
The values returned are Match objects, not strings. (They conveniently automatically coerce to strings if treated as strings, but the difference sometimes matters, and greatly expands what Raku can make possible/easy with the returned matches.)
Many operations return a single Match object when Perl folk will expect a list. Your case is an example. But what exactly is a single object? You are a single person. But you're also more than that.
Some objects in Raku are a "parent" of "child" objects. (Match objects are a case in point.) Children are typically accessible by applying appropriate operations to a parent. Your case is an example. .list will often work as an idiomatic way to get at numbered children, .hash for named children. Another approach among many that will work if a given class/object supports it is subscripting:
say ('foo' ~~ m{ (.) (.) })[*]; # (「f」 「o」)
say ('foo' ~~ m{ (.) (.) $<I'm-named>=(.) })<I'm-named>; # 「o」
Why the additional complexity? For Match objects it's a trade off to make the tremendous additional power and utility of general parsing automatically and easily available. See Extracting from .bib file with Raku for more discussion of this.
You'll never do wrong following Raiph's answers. But I'm going to try and explain a bit why that works, and propose alternatives. A Match is a Capture, a beast that's essentially a list and an associative array at the same time. What you want is to have the result as a list, same as it happens in Perl. How do you extract the "list" part (or coerce it to a list)? Easy: use explicit coercion (as Raiph suggests) or try and put it in a list context. What creates a list context? Flattening does:
my ($a, $b, $c, $d, $e) = |('hello' ~~ m{ ^(h) (e) (l) (l) (o) });
List binding does too:
my ($a, $b, $c, $d, $e) := 'hello' ~~ m{ ^(h) (e) (l) (l) (o) };
The thing is that every one of the variables will still contain a Match; what you probably want is the self same thing as in Perl, a list of strings. We can get that with this combo:
my #array is Array[Str(Match)] = | ('hello' ~~ m{ ^(h) (e) (l) (l) (o) });
Where we get the list context in the right hand side, and by explicitly declaring an array with coercing elements, we get [h e l l o] as a result.

Getting "value without a container" error

Got this:
for $config.IO.slurp.lines <-> $l {
$l .= trim;
...
}
Get this:
t/01-basic.rakutest ..3/5
Parameter '$l' expects a writable container (variable) as an argument,
but got '# karabiner config file' (Str) as a value without a container.
in sub generate_file at...
I've read the docs on containers but it didn't shed any light on what I can do in this situation aside from maybe assigning $l to a scalar variable, which seems hacky. Is there a way I can containerize $l?
The issue is really that .lines does not produce containers. So with <->, you would bind to the value, rather than a container. There are several ways to solve this, by containerizing as you suggested:
for $config.IO.slurp.lines -> $l is copy {
$l .= trim;
...
}
But that only makes sense if you want to do more changes to $l. If this is really just about trimming the line that you receive, you could do this on the fly:
for $config.IO.slurp.lines>>.trim -> $l {
...
}
Or, if you need to do more pre-processing $l, use a .map:
for $config.IO.slurp.lines.map({
.trim.subst("foo","bar",:g)
}) -> $l {
...
}
Maybe below is what you want? Generally, you read a file via slurp you can comfortably handle its size, or you read a file via lines if you want input taken in lazily, one-line-at-a-time:
my $config = 'alphabet_one_letter_per_line.txt';
my $txt1 = $config.IO.slurp;
$txt1.elems.say; #1
$txt1.print; #returns alphabet same as input
my $txt2 = $config.IO.lines;
$txt2.elems.say; #26
$txt2.join("\n").put; #returns alphabet same as input
Above, you get only 1 element when slurping, but 26 elements when reading lines. As you can see from the above code, there's no need to "...(assign) $l to a scalar variable..." because there's no need to create (temporary variable) $l.
You can store text in #txt arrays, and get the same number of elements as above. And you can just call routines on your stored text, as you have been doing (example below continues $txt2 example above):
$txt2.=map(*.uc);
say $txt2;
Sample Output:
(A B C D E F G H I J K L M N O P Q R S T U V W X Y Z)
[Note, this question seems to have triggered questions on the use of $txt2.=map(*.uc); versus $txt2.=uc;. My rule-of-thumb is simple: if the data structure I'm working on has more than one element, I map using * 'whatever-star' to address the routine call to each element].
https://docs.raku.org/

Is there a 'clamp' method/sub for ranges/Num etc in Raku (i.e. Perl6)?

Is there a 'clamp' or equivalent method or sub in Perl6?
eg
my $range= (1.0 .. 9.9)
my $val=15.3;
my $clamped=$range.clamp($val);
# $clamped would be 9.9
$val= -1.3;
$clamped=$range.clamp($val);
# $clamped would be 1.0
Another tact you might like to explore is using a Proxy, which allows you to define "hooks" when fetching or storing a value from a container
sub limited-num(Range $range) is rw {
my ($min, $max) = $range.minmax;
my Numeric $store = $min;
Proxy.new(
FETCH => method () { $store },
STORE => method ($new) {
$store = max($min, min($max, $new));
}
)
}
# Note the use of binding operator `:=`
my $ln := limited-num(1.0 .. 9.9);
say $ln; # OUTPUT: 1
$ln += 4.2;
say $ln; # OUTPUT: 5.2
$ln += 100;
say $ln; # OUTPUT: 9.9
$ln -= 50;
say $ln; # OUTPUT: 1
$ln = 0;
say $ln; # OUTPUT: 1
This particular limited-num will initialise with it's min value, but you can also set it at declaration
my $ln1 := limited-num(1.0 .. 9.9) = 5.5;
say $ln1; # OUTPUT 5.5;
my $ln2 := limited-num(1.0 .. 9.9) = 1000;
say $ln2; # OUTPUT 9.9
I don't think so. So, perhaps:
multi clamp ($range, $value) {
given $range {
return .max when (($value cmp .max) === More);
return .min when (($value cmp .min) === Less);
}
return $value
}
my $range = (1.0 .. 9.9);
say $range.&clamp: 15.3; # 9.9
say $range.&clamp: -1.3; # 1
my $range = 'b'..'y';
say $range.&clamp: 'a'; # b
say $range.&clamp: 'z'; # y
The MOP allows direct exploration of the objects available in your P6 system. A particularly handy metamethod is .^methods which works on most built in objects:
say Range.^methods; # (new excludes-min excludes-max infinite is-int ...
By default this includes just the methods defined in the Range class, not the methods it inherits. (To get them all you could use say Range.^methods: :all. That'll net you a much bigger list.)
When I just tried it I found it also included a lot of methods unhelpfully named Method+{is-nodal}.new. So maybe use this instead:
say Range.^methods.grep: * !~~ / 'is-nodal' /;
This netted:
(new excludes-min excludes-max infinite is-int elems iterator
flat reverse first bounds int-bounds fmt ASSIGN-POS roll pick
Capture push append unshift prepend shift pop sum rand in-range
hyper lazy-if lazy item race of is-lazy WHICH Str ACCEPTS perl
Numeric min max BUILDALL)
That's what I used to lead me to my solution above; I sort of know the methods but use .^methods to remind me.
Another way to explore what's available is doc, eg the official doc's Range page. That netted me:
ACCEPTS min excludes-min max excludes-max bounds
infinite is-int int-bounds minmax elems list flat
pick roll sum reverse Capture rand
Comparing these two lists, sorted and bagged, out of curiosity:
say
<ACCEPTS ASSIGN-POS BUILDALL Capture Numeric Str WHICH append
bounds elems excludes-max excludes-min first flat fmt hyper
in-range infinite int-bounds is-int is-lazy item iterator
lazy lazy-if max min new of perl pick pop prepend push
race rand reverse roll shift sum unshift>.Bag
∩
<ACCEPTS Capture bounds elems excludes-max excludes-min flat
infinite int-bounds is-int list max min minmax pick
rand reverse roll sum>.Bag
displays:
Bag(ACCEPTS, Capture, bounds, elems, excludes-max, excludes-min,
flat, infinite, int-bounds, is-int, max, min, pick,
rand, reverse, roll, sum)
So for some reason, list, minmax, and sum are documented as Range methods but are not listed by my .^methods call. Presumably they're called Method+{is-nodal}.new. Hmm.
say Range.^lookup('minmax'); # Method+{is-nodal}.new
say Range.^lookup('minmax').name; # minmax
Yep. Hmm. So I could have written:
say Range.^methods>>.name.sort;
(ACCEPTS ASSIGN-POS AT-POS BUILDALL Bag BagHash Capture EXISTS-POS
Mix MixHash Numeric Set SetHash Str WHICH append bounds elems
excludes-max excludes-min first flat fmt hyper in-range infinite
int-bounds is-int is-lazy item iterator lazy lazy-if list max min
minmax new of perl pick pop prepend push race rand reverse roll
shift sum unshift)
Anyhow, hope that's helpful.
Strange that no one has suggested using augment. Admittedly, it creates global changes, but that might not be an issue.
augment class Range {
method clamp ($value) { ... }
}
You will need to use the pragmause MONKEY-TYPING in the same scope before the augment in order to use it though. But this way, you can simply say $range.clamp(5), for instance. It saves you one character over raiph's answer, but at the (not insignificant) cost of breaking precompilation.

Array stays array when looped through `for`

I'm looping through the depends array from a META6.json. I've loaded into a Hash using JSON::Fast. When I'm looping through it using a for loop, however, it only goes through the loop once, and the item is the same array:
use JSON::Fast;
my %meta = from-json(slurp("META6.json"));
for %meta<depends> -> $dependency {
dd $dependency;
}
This piece of code returns
Array $dependency = $["Config::Parser::toml:ver<1.0.1+>", "Config:api<1>:ver<1.3.5+>", "Dist::Helper:ver<0.21.0+>", "Hash::Merge", "Terminal::Getpass:ver<0.0.5+>", "zef"]
I'm expecting it to loop through the %meta<depends> 6 times, which each iteration holding a different element from that array.
For good measure, this is the output of dd %meta<depends> from the example:
Array %meta = $["Config::Parser::toml:ver<1.0.1+>", "Config:api<1>:ver<1.3.5+>", "Dist::Helper:ver<0.21.0+>", "Hash::Merge", "Terminal::Getpass:ver<0.0.5+>", "zef"]
Why is the loop not looping the way I expected?
EDIT: I'm using the latest Rakudo Star:
This is Rakudo Star version 2018.04.1 built on MoarVM version 2018.04.1
implementing Perl 6.c.
Even though %meta<depends> contains an Array, it is contained inside an item (container). The for statement looks at that and decides there's only 1 thing to iterate over (the container).
This is easily remedied: by suffixing .list you convert the item to something Iterable, and thus it will iterate over the Array in the container:
for %meta<depends>.list -> $dependency {
A slightly shorter syntax for this is #():
for #(%meta<depends>) -> $dependency {
EDIT: or use the syntax suggested by jjmerelo, which decontainerizes the element, and thus exposes the underlying Array to for:
for %meta<depends><> -> $dependency {
This is a pitfall. Essentially this is like:
my $var = ['a', 'b', 'c'];
for $var -> $v {
dd $v;
}
Which gives you: $["a", "b", "c"]
If you iterate an array with # sigil it already acts as an Array, but when you have a list inside a scalar it will return the Array and not iterate inside it.
The solution is to use .list to make it act as a list instead of as a scalar.

Perl6: NCurses and mouse events

I do not succeed in getting returned a mouse event from getmouse. Are there errors in my code?
#!/usr/bin/env perl6
use v6;
use NCurses;
my $win = initscr;
raw();
keypad( $win, True );
my Array[int32] $old;
mousemask( ALL_MOUSE_EVENTS +| REPORT_MOUSE_POSITION, $old ) or die;
loop {
my $key = getch();
if $key == KEY_MOUSE {
my NCurses::MEVENT $event;
my $ok = getmouse( $event );
endwin;
say "ok: ", $ok.perl; # -1
say "event: ", $event.perl; # NCurses::MEVENT
exit;
}
}
NCurses
The usual idiom
If you have to write a type's name as part of a variable declaration you might as well write it as the variable's type constraint and use .= new rather than using either Marty's or your solution:
my NCurses::MEVENT $event .= new
Marty's solution:
my $event = NCurses::MEVENT.new
works because $event now contains what the getevent($event) call needs it to contain, namely a new NCurses::MEVENT object. But it passes up an easy opportunity to add type-checking.
Your solution:
my NCurses::MEVENT $event = NCurses::MEVENT.new
means the same as the usual idiom for this situation but isn't DRY.
What went wrong
The line of code that glues the Perl 6 getmouse call to the underlying native NCurses library starts:
sub getmouse(MEVENT) ...
This tells Perl 6 that getmouse needs one argument of type NCurses::MEVENT.
Marty's solution worked. They didn't tell Perl 6 what type of value $event should contain. But they put the right value in it anyway so the lack of type-checking didn't matter.
Your original solution enabled useful type-checking. It told Perl 6 to make sure that $event only ever contained an object of type NCurses::MEVENT. Unfortunately you didn't explicitly initialize the variable so it contained...
Hang on. What did it contain? Shouldn't Perl 6 have made sure that there was an NCurses::MEVENT object in $event anyway?
In fact it did! But rather than putting an ordinary new NCurses::MEVENT object in there Perl 6 put an old NCurses::MEVENT type object in there as a placeholder. Among other things, Type objects represent the notion of an uninitialized object. (A bit like "undef" in Perl 5.)
This mechanism normally works well for surfacing errors like forgetting to suitably initialize a variable. But not in this case. So what went wrong?
Back to the getmouse declaration. It should have been:
sub getmouse(MEVENT:D) ...
The :D "type smiley" would tell Perl 6 that the argument has to be defined, ie that an uninitialized NCurses::MEVENT isn't good enough. With this smiley you'd have gotten an appropriate error rather than silence.
A less-than-awesome silent failure is masking the fact that you're passing a type object into getmouse(). I only found it by substituting $event.perl with $event.x on line 18 as an information fishing expedition. Doing that produces;
user#Ubuntu-iMac:~$ ./getmouse.p6
ok: -1
Invocant requires an instance of type NCurses::MEVENT, but a type object was passed. Did you forget a .new?
in block at ./getmouse.p6 line 17
...which is just a little more informative.
I'm sure you get it now but, for the record, you typed the $event variable but didn't assign any value to it, so it gets the type object which according to the Perl6 class tutorial is an undefined, "empty instance" of the type.
By simply substituting my $event = NCurses::MEVENT.new; for my NCurses::MEVENT $event; on line 13, one gets;
user#Ubuntu-iMac:~$ ./getmouse.p6
ok: 0
event: NCurses::MEVENT.new(id => 0, x => 70, y => 26, z => 0, bstate => 128)
... and all is well with the world.
I've found the missing part:
#!/usr/bin/env perl6
use v6;
use NCurses;
my $win = initscr;
raw();
keypad( $win, True );
my Array[int32] $old;
mousemask( ALL_MOUSE_EVENTS +| REPORT_MOUSE_POSITION, $old ) or die;
loop {
my $key = getch();
if $key == KEY_MOUSE {
my NCurses::MEVENT $event = NCurses::MEVENT.new;
my $ok = getmouse( $event );
endwin;
say "ok: ", $ok.perl;
say "event: ", $event.perl;
exit;
}
}