Access callee/this in Raku map code/block

Viewed 270

Is it possible to access the "callee" ("this") in Raku map code block and/or whatevercode?

E.g. if one wanted to calculate arithmetic mean

my $data = <1 10 0 7 2> ;
say $data.sum
  / $data ;   # or @$data, same as $data.elems in this numeric context
# OUTPUT: «4␤»

by sum of fractions

say $data.map(* / $data).sum
# OUTPUT: «4␤»

like

<1 10 0 7 2>.map({ $_ / $this }).sum   # but there's no $this

or

<1 10 0 7 2>.map( * / $this ).sum

If not, is there an idiomatic way to do it with only a chain of method calls on the <…> data literal, or different way, without storing the data in variable?

3 Answers

There is not, to my knowledge. You would need to be able to know how many values an iterator will produce.

Under the hood, a foo.map: &callable is basically a:

my $iterator = foo.iterator;
callable($iterator.pull-one) until iterator exhausted;

Note that all knowledge of the source of the iterator is lost at that point: there's just an iterator producing values.

Some types of iterators (the so-called PredictiveIterators can tell you how many values it can still produce without actually producing them. But most iterators are not able to do that.

I guess technically it would be possible for map to check whether it got a PredictiveIterator and set a dynamic variable with the number of values it will produce, which you could then check inside the &callable.

Still, it doesn't feel that this is worth the extra check for all maps and for loops, so I don't think a PR for such a feature would be accepted.

you could do this, by andthen

<1 10 0 7 2>
andthen .sum / .elems
andthen .say

or

<1 10 0 7 2>
andthen .map: * / .elems
andthen .sum
andthen .say

As Liz mentioned, there's no variable that's auto-bound to the value being iterated over, but there are several ways to do what you want. Here are three ways to do so by binding the series to the topic variable (the first is most concise and is probably what I'd write):

<1 10 0 7 2>.&{ .map(* / $_).sum };
given <1 10 0 7 2> { .map(* / $_).sum };
with <1 10 0 7 2> { .map(* / $_).sum };

Another option would be to duplicate the initial list with xx. Here's one way that could look, though I'm not huge fan of it in this case.

(<1 10 0 7 2> xx 2).map({|$^a.map: * / $_}).sum;

Similarly, you could zip each element with the list as a whole (which would be overkill in this simple case, but might be useful in similar situations.)

Related