Author Archive

## Most trusting

May 21, 2023 3 comments

I was looking for a neat way to specify units when working with numbers. When doing dimensional analysis, many physicists like to put units into square brackets to create an additional namespace. We can do the same.

``````use v6.d;

class Unit { ... }

class SiInt is Int {
trusts GLOBAL;
trusts Unit;

has Unit \$!unit;
method !unit { return-rw \$!unit }

method new(CORE::Int \$new) { nextsame }
method Str { self.Int::Str ~ \$!unit.suffix }

method ACCEPTS(Unit:U \$u) { \$!unit === \$u }
}

class Unit {
our \$.suffix = '';
our \$.long-name = "unit-less";

method ACCEPTS(SiInt \$si) { \$si!SiInt::unit === self }
}

class m is Unit { our \$.suffix = 'm'; our \$.long-name = 'Meter'; }

multi sub postcircumfix:<[ ]>(SiInt \$obj, Unit:U \$unit) {
\$obj!SiInt::unit === Unit ?? (\$obj!SiInt::unit = \$unit)
!! fail(‘Sorry, units can only be set, not changed.’);

\$obj
}
multi sub postcircumfix:<[ ]>(Int \$value, Unit:U \$unit) { SiInt.new(\$value)[\$unit] }

constant Int = SiInt; # intentional shadowing of CORE::Int

my \$i = 42[m];
put [\$i, \$i.^name]; # 42m SiInt
my Int() \$a = 1;
put [\$a, \$a.^name]; # 1 SiInt

class s is Unit { our \$.suffix = 's'; our \$.long-name = 'Seconds'; }
multi sub infix:<+>(SiInt \$l, SiInt \$r) {
\$l!SiInt::unit === Unit ?? callsame()[\$r!SiInt::unit]
!! \$r!SiInt::unit === Unit ?? callsame()[\$l!SiInt::unit]
!! \$l!SiInt::unit === \$r!SiInt::unit ?? nextsame()
!! fail(„Unit mismatch between \$l and \$r“)
}

my \$s = 60[s];

say \$i + \$a; # 43m
say \$s + \$i; # Unit mismatch between 60s and 42m
``````

The idea is to have a numerical type that is by default unit-less. A unit can be added (but not changed) with square bracket postcircumfix. Since I add type-objects for each unit, I don’t have to mess around with strings and can multi-dispatch if needed. Since I want direct access to the unit, I tell the class to trust the package the operators are defined in. (This could be a module, of course.) What happens to be an ENODOC.

I have to use a forward declaration to get `ACCEPTS` to get hold of `\$!unit`. Subsequently, multi-dispatch works just fine.

``````multi sub fancy(Int \$value where m) { #`[fancy calculation goes here] }
multi sub fancy(Int) { fail ‘Can only be fancy with Unit "m".’ }
fancy(\$i);``````

Since `SiInt` is just an `Int` all built-ins will work, so long the unit is restored after using them. Being able to trust operators allows them to access the entire class, without having to cheat with `use nqp;`.

Because Raku treats types as values, I can calculate a compound unit.

``````class Meters-per-Second is Unit { our \$.suffix = 'm/s'; our \$.long-name = 'Meters per Second'; }
multi sub infix:</>(m, s) { Meters-per-Second }

sub speed(\$d where m, \$t where s) { (\$d / \$t).Int.[m/s] }

my Int \$fast = speed(500[m], 1[s]);
say \$fast; # 500m/s``````

I’m quite pleased with being able to extend the type-system so easily without having to invent a complete new DSL. This aids composability greatly.

Categories: Raku, Uncategorized

## Plucking strings

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 `Set`s. 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 `Str`s and turn them into `Set`s and `Str`s. 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

## Halve an algorithm

May 7, 2023 1 comment

I considered PWC #215 to be to boring to be solved because it basically requires only halve an algorithm. But that idea kept bouncing around in my head.

``````my @words := <abc xyz tsu> does role { has Int \$.count is rw; };
@words.map({ LAST { say @words.count }; if [before] .comb { .item  } else { @words.count++; Empty } });
``````

Part 1 is a two sentence problem that can be solved with two lines, because Raku splits sorting algorithms into two smaller problems. We got `infix:<before>` and `infix:<after>` to describe what ordering in a list actually means. In my eyes Rosettacode proves this to be the right decision. Here, we are asked to check for sorted-ness, without actually having to sort anything. Lesser languages need to resort to loops, we can use a reduction metaop with `before` to express our wish. So I either retain an element or count the miss and skip the element.

I resorted to mixing in a role because I don’t like to stack symbol declarations on one line. By getting rid of `my \$count`; I can stay within my 2 line limit. For the same reason I used a `LAST`-phaser. By accident I bound two related things together into the same namespace, which I rather liked in the end.

So far I did not see Raku’s ordering operators in code in the wild, other then Rosettacode. To promote them, I wrote this blogpost.

I’m not sure if the reaction of `map` to `Empty` is an ENODOC. It might just be emergent behaviour and I shall seek wisdom in #raku-doc.

Categories: Raku

## Not even empty

April 23, 2023 2 comments

Another weekend, another rabbit hole. While reading Arne’s solution for challenge #213, I thought to myself: “If the task can be written in one sentence, why does the answer takes more then one line?”. A reasonable question, given the answer is written in Raku. After some wiggling, I found a shorter version.

``````my @list = <1 2 3 4 5 6>;
@list.classify({ .Int %% 2 ?? 'even' !! 'odd'}){'even','odd'}».sort.put;``````

To use `.classify`, as Arne did further down, was the key, because `postcircumfix:<{ }>` takes more then one key. The resulting lists are hyper-`.sort`ed and `.put` will not only show more then 100 elements (please don’t use `say` unless you actually want that) but will also flatten.

Futher on in his post, he tackled some error handling. I wanted that too and to my delight `X::Str::Numeric` stores the inconvertible thing inside `.source`.

``````CATCH {
when X::Str::Numeric { put „Sorry, I don't know how to handle "{.source}" in my input list.“ }
}
``````

But how would I deal with undefined values? This is not far-fetched. If you get some CSVs that popped out of a less-then-optimal Excel sheet or don’t take the possibility of SQL `null` into account, you can easily end up with a type-object where you don’t want one. For scalars we can guard code with type-smilies.

``````sub foo(@a) { say @a.elems };
foo(List); # 1``````

For me an `Array` or a `List` are a place where a (semi-infinitely long and very narrow) box, that could contain stuff, could have been, but isn’t. Rakudo doesn’t care and just binds the undefined value to the @-sigiled symbol. As usual, I was enlightened on IRC. `Any.list` will try its best to turn a single element into a `List` with a single element. For `42` that makes perfect sense and allows us to worry less in day-by-day code. For type-objects that leads the question: “How many things are stored in you?”, to be answered with nonsense. I wouldn’t be surprised to learn that this leads to hard to track down bugs. My `.t`-files typically don’t sport copious testing against undefined values, because I falsely believed that `:D` would safe my bum.

``````sub bar(@a where .[0].defined) { say @a.elems };
bar(List);``````

This is ugly, imprecise and doesn’t really do what I want. When the `where`-clause is triggered the binding has already happened. I have the feeling that the right solution is to let `Any.list` fail when called on a type object. As lizmat pointed out, that is a breaking change. It may look benign but there is tons of fault-tolerant-by-accident code out there that relies on sloppy handling of undefined values. In the IRC discussion, lizmat stated she didn’t like the `my @a := Int;` case. I’m actually fine with that, because the intent of the Raku-programmer (and we want more of those, don’t we?) is clear. The silent case when @-sigiled symbols (not @-sigiled containers!) in `Signature`s are bound to type-objects worries me. It is certainly possible to change that but may have performance implications. It too could be a breaking change and it is hard to tell how many lurking bugs we would squash with a fix.

Yet, I would really like Raku to be WAT-free, because I surely want Raku to be more sticky.

UPDATE:

The following can be used to make `.elems` fail early. That `prefix:<+>` doesn’t go through that method leads to `+@a` return 0 while `.elems` is 1. Also, `Parameter.modifier` is an ENODOC.

``````    Any.^can('elems')[0].candidates.grep({ .signature.params[0].&{ .type, .modifier } ~~ (Any, ':U') }).head\
.wrap(my method elems { die(‘Abstract containters don't know how to count’) });``````
Categories: Raku

## Yes, but don’t!

masukomi, likes to play with fire and who am I to stop him? In fact, I shall aid him by answering his question: “#RakuLang is there a way to augment / monkeypatch a class to give it another parent class ?”.

There are a few obstacles. First, a class tends to be composed when we get hold of it and secondly, the list of parents is in fact a `List`. Both problems vanish when we `use nqp`.

``````class A1 { }
class A2 { method cheating { say ‘Yes, but don't!’ } }

class B is A1 { }

use nqp;

my \parents := nqp::getattr(B.HOW, Metamodel::ClassHOW, '@!parents');
nqp::push(parents, A2);
B.^compute_mro;
B.^compose;

dd parents; # (A1, A2)
say B.^mro; # ((B) (A1) (A2) (Any) (Mu))

B.new.cheating; # Yes, but don't!``````

In addition to re-`.compose` we have to re-compute the MRO, as there is some caching going on. In fact, you should expect to upset the compiler quite a bit when fooling around with things that are meant to be rather static. If you burn yourself with this spell … well, it’s a level 9 fire spell after all.

The proper way to get the same result would be as follows.

``````use MONKEY;

class A3 { }

augment class B { also is A3 }

# ===SORRY!=== Error while compiling /home/dex/projects/raku/tmp/2021-03-08.raku
# Parents cannot be added to class 'B'after it has been composed
``````

If I can muster the courage I shall challenge jnthn with a Rakubug.

Categories: Raku

## Recursive Cinderella

October 3, 2022 1 comment

PWC 184 asked to split an array into numbers and letters.

``````for ( 'a 1 2 b 0', '3 c 4 d'), ( '1 2', 'p q r', 's 3', '4 5 t') -> @a is copy {
@a.=map: *.split(' ').cache;
my @numbers = @a.deepmap({ / <[0..9]> / ?? .Numeric !! Empty }).grep(+*)».Array;
my @letters = @a.deepmap({ / <[a..z]> / ?? .Str !! Empty }).grep(+*)».Array;
say @numbers, ‘ and ’, @letters;
}``````

I use `deepmap` with `Empty` to separate to weed from the chaff and remove the extra structure added by `deepmap` with a `grep`. That extra structure raised the question, what would be needed to split a tree in twain. This could be useful to gain parts of a XML Document with a single parser pass, while maintaining the structure of the tree. Relation of child nodes and parent nodes often carries meaning.

``````for ( 'a 1 2 b 0', '3 c 4 d'), ( '1 2', 'p q r', 's 3', '4 5 t'), [ '1', [ [ 'a' ], '2 b'], '3 c' ] -> @a is copy {

multi sub cinderella(@data, @a, \$needle-a, @b, \$needle-b) {
for @data {
@a[\$++] := my \$new-a;
@b[\$++] := my \$new-b;
cinderella(\$_, \$new-a, \$needle-a, \$new-b, \$needle-b)
}
}

multi sub cinderella(@data, Any:U \$a is raw, \$needle-a, Any:U \$b is raw, \$needle-b) {
my @new-a;
my @new-b;
for @data {
cinderella(\$_, \$a, \$needle-a, \$b, \$needle-b);
@new-a.push: \$a;
@new-b.push: \$b;
}
\$a = @new-a;
\$b = @new-b;
}

multi sub cinderella(Str:D \$s, \$a is raw, \$needle-a, \$b is raw, \$needle-b) {
cinderella(\$_, my @new-a, \$needle-a, my @new-b, \$needle-b) for \$s.split(' ');
\$a = @new-a ?? @new-a.join(' ') !! Empty;
\$b = @new-b ?? @new-b.join(' ') !! Empty;
}

multi sub cinderella(Str:D \$s where *.chars == 1, @a, \$needle-a, @b, \$needle-b) {
given \$s {
when \$needle-a { @a.push: \$s }
when \$needle-b { @b.push: \$s }
default { fail('dunno where to put: ' ~ \$s) }
}
}

cinderella @a, my @numbers, / <[0..9]> /, my @letters, / <[a..z]> /;

dd @numbers, @letters;
}

# OUTPUT: Array @numbers = ["1 2 0", "3 4"]
Array @letters = ["a b", "c d"]
Array @numbers = ["1 2", Empty, "3", "4 5"]
Array @letters = [Empty, "p q r", "s", "t"]
Array @numbers = ["1", [[[],], "2"], "3"]
Array @letters = [[], [["a"], "b"], "c"]``````

The leaves in the target tree can be either a `Str` or (empty) `Array`. However, the first multi candidate doesn’t make the decision what item is added to the target `Array`s. Instead I create a fresh scalar container and bind it to a container within the `Array`s. I then pass that container on to the next `multi`-candidate where it might be filled with `Positional`– or `Str`-object. Please note the distinction. MMD doesn’t care about the container type we use, it looks for the properties of the value. This allows me to split the strings on a white space and pass it on into the next round of MMD matches. The 2nd candidate handles the case where we descent into a nested `Array`. It can manipulate the scalar container created with `@a[\$++] := my \$new-a;` and turn it into a Positional value (here an `Array`), because that initial container is passed into the multi with `is raw`.

This is a very powerful concept. Writing the same with a single recursive function would contain a lot of special casing and be no joy to debug.

Not doing what the instructor asked for seems to produce better results. I shall do so more often.

Categories: Raku

## Rabbitholeing

September 29, 2022 1 comment

With PWC 182-2 specifically asking for Linux paths to be handled, we need to resolve issues like `/../` and symbolic links. Since I didn’t feel like putting a directory called `a` into my root folder, I wrote a little helper that deals with some of the tripwires that modern filesystems provide.

``````my @input = qw <
/a/b/c/1/x.pl
/a/b/c/d/e/2/x.pl
/a/b//c/d/3/x.pl
/a/b/./c/4/x.pl
/a/../a/b/c/d/5/x.pl
>;

sub resolve(Str:D() \$s){
my @parent-refs = (my @parts = \$s.split(rx{ ‘/./’ | ‘/’+ })).pairs.grep(*.value eq '..')».key;
@parent-refs = flat @parent-refs, @parent-refs »-» 1;
@parts[@parent-refs]:delete;
@parts.join(‘/’)
}

# OUTPUT: /a/b/c/1/x.pl /a/b/c/d/e/2/x.pl /a/b/c/d/3/x.pl /a/b/c/4/x.pl ///a/b/c/d/5/x.pl``````

The last path starts with a triple root, because `join` assumes holes are actually there. It won’t skip fields that are `Empty` either, so `is default(Empty)` doesn’t help. To actually remove elements from an `Array` we are better of with `splice`. Since this method doesn’t return `self`, we can’t just `@parts.splice(@parent-refs.any,1).join(‘/’)`. Both ways to remove elements are mutators and eager. That doesn’t fit well into the rest of the language and spells doom for concurrency. To find a solution I had to go down the rabbit hole that is iteration in Rakudo. The bottom happens to be located in Rakudo/Iterator.pm6.

``````method pull-one() is raw {
nqp::ifnull(
nqp::atpos(\$!reified,++\$!i),
nqp::if(
nqp::islt_i(\$!i,nqp::elems(\$!reified)), # found a hole
self!hole(\$!i),
IterationEnd
)
)
}``````

So a hole in an `Array` is just a null-pointer in C-land — given that we didn’t overshoot the end of the `Array`. With that knowledge, building an Iterator that skips elements becomes rather simple.

``````multi sub prune(@a, Int:D \$i --> Seq:D) {
prune @a, \$i .. \$i
}

multi sub prune(@a, +@l is copy --> Seq:D) {
@l = @l.sort; # this makes checking the index against the needles simpler

Seq.new: class :: does Iterator {
has int \$!i;
has \$!reified;
submethod !SET-SELF(\arr) {
\$!reified := nqp::getattr(@a,List,'\$!reified');
\$!i = -1;
self
}
method new(\arr) { nqp::create(self)!SET-SELF(arr) }
method pull-one is raw {
loop {
++\$!i;
if @l {
@l.shift while +@l && \$!i > @l[0].max;
next if +@l && @l[0].min ≤ \$!i ≤ @l[0].max;
}
return nqp::ifnull(
nqp::atpos(\$reified, \$!i),
nqp::if(
nqp::isge_i(\$!i, nqp::elems(\$reified)),
IterationEnd,
next # we actually got a hole
)
);
}
}
}.new(@a)
}

@a = ('a' .. 'z').List;

dd @a.&prune( 25 );
dd @a.&prune( 10..15 );
dd @a.&prune( (2,3,10..15, 21..22, 25).pick(5) ); # randomising for testing
# 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").Seq
#         ("a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z").Seq
#         ("a", "b", "e", "f", "g", "h", "i", "j", "q", "r", "s", "t", "u", "x", "y").Seq``````

The idea is to loop by default and bail out of that loop if the current index held in `\$!i` is not found in any needle. Since we don’t got real `goto` in Raku, `loop/next` becomes a decent substitute. As I don’t really understand what a unreified `Array` is, I’m right now not qualified to provide a PR.

Dealing with lazy and infinite lists makes `Array` a beast. I shall not falter until I have tamed it!

Categories: Raku

## Valid temperatures

September 5, 2022 1 comment

PWC 181 Task 2 asks for input validation of dates and numbers. OK, it should ask for input validation so my solution isn’t totally over the top. Better safe then sorry.

``````my \$data = q:to/EOH/;
2022-08-01, 20
2022-08-09, 10
2022-08-03, 19
2022-08-06, 24
2022-08-05, 22
2022-08-10, 28
2022-08-07, 20
2022-08-04, 18
2022-08-08, 21
2022-08-02, 25
EOH

\$data.lines()
==> map(* ~~ / \$<date> = [ \d+ ‘-’ \d?\d ‘-’ \d?\d ] \s* ‘,’ \s* \$<temperature> = [ '-'?\d+ [ '.' \d+ ]? ] /)
==> map(-> (Date(Str:D(Match)) :\$date, Numeric(Str:D(Match)) :\$temperature, *%) { [ \$date, \$temperature ] })
==> sort(*.first)
==> map( -> [\$date, \$temperature ] {
state \$last-temp;
LEAVE \$last-temp = \$temperature;
once next;
„\$date, Δ{abs \$last-temp - \$temperature}°C“ if \$temperature > \$last-temp;
})
==> -> *@a { @a.join(\$?NL) }()
==> put();``````

First I use a regex with named captures to get hold of the date and temperature `Str`s. Those are not overly helpful, as I need to sort by date. The 2nd map solves that problem with nested coercions in a destructuring sub-signature. As `Match` contains a few extra methods that I don’t care for, I need to gobble those up with an anonymous hash slurpy. Now I have a LoL with a date and temperature sub-list, where the date is a `Date` and the temperature is something `Numeric`. I sort by the first and then destructure again. I use a `state` container to keep a link between consecutive iteration steps. (To make this `hyper`-friendly, we would need to `.rotor`.) I always want `\$last-temp` to contain the `\$temperature` of the previous iteration but I don’t want to do anything else in the first iteration. Hence, the `LEAVE`-phaser and the `once`. Anything else means to create the output text. Since I don’t want a combo breaker, I use a pointy-block like we need to in a feed-operator-chain to add newlines where they belong.

If you managed to stuff more Raku-features into your solution, you out-convoluted me and I would love to see your code.

Joking aside, I believe to have another candidate for `macro-ideas.txt`. If we look at the first and second map, there is a pattern. We use the same named-arguments in both. With RakuAST it might be possible to provide a `Regex`, a block and a list of coercions, to produce a `Sub` that does that in one go. That would be a neat shortcut for simple parsers that deal with lines of text.

Categories: Raku

## Assuming optionality

September 4, 2022 1 comment

PWC 180 Task 1 asks us to find the first unique character in a string. I wanted to have a nice interface where I would write:

``say \$str.comb.first: &unique-char;``

The idea was to curry `postcircumfix:<{ }>` so it will be bound to a `BagHash` and always ask for `:!exists`. Alas, `.assuming` doesn’t do the right thing if the proto contains optional positions. I found a workaround utilising `once`.

``````for ‘Perl Weekly Challenge’, ‘Long Live Perl’ -> \$str {
my &unique-char = { (once \$str.comb.BagHash»--){\$_}:!exists }
say \$str.comb.first: &unique-char;
}``````

I don’t want to build the `BagHash` and remove single elements every time `unique-char` is called. There is a slight overhead when using `once` but that beats `.assuming` by a mile.

Given all the special cases `Signature`s provide, we may want to consider turning `.assuming` into a RakuAST-macro.

Categories: Raku