Archive
There Is More Than One Way At The Same Time
The Perl 6 Rosattacode section for parallel calculations is terribly outdated and missing all the goodies that where added or fixed in the last few weeks. With this post I would like to propose an updated version for Rosettacode. If you believe that I missed something feel free to comment below. Please keep in mind that Rosettacode is for showing off, not for being comprehensive.
use v6.d.PREVIEW;
Perl 6 provides parallel execution of code via threads. There are low level custructs that start a thread or safely pause execution.
my $t1 = Thread.start({ say [+] 1..10_000_000 }); my $t2 = Thread.start({ say [*] 1..10_000 }); $t1.finish; $t2.finish; my $l = Lock.new; $l.lock; $t1 = Thread.start: { $l.lock; say 'got the lock'; $l.unlock }; sleep 2; $l.unlock; $t1.finish;
When processing lists, one can use a highlevel Iterator
created by the methods hyper
and race
. The latter may return values out of order. Those Iterator
s will distribute the elements of the list to worker threads that are in turn assigned to OS level threads by Rakudos ThreadPoolScheduler
. The whole construct will block until the last element is processed.
my @values = 1..100; sub postfix:<!> (Int $n) { [*] 1..$n } say [+] @values.hyper.map( -> $i { print '.' if $i %% 100; $i!.chars });
For for
-lovers there are the race for
and hyper for
keyword for distributing work over threads in the same way as their respective methods forms.
race for 1..100 { say .Str; # There be out of order dragons! } my @a = do hyper for 1..100 { .Int! # Here be thread dragons! } say [+] @a;
Perl 6 sports constructs that follow the reactive programming model. One can spin up many worker threads and use threadsafe Channel
s or Supply
s to move values from one thread to another. A react
-block can combine those streams of values, process them and react to conditions like cleaning up after a worker thread is done producing values or dealing with errors. The latter is done by bottling up Exception
-objects into Failure
-objects that keep track of where the error first occured and where it was used instead of a proper value.
my \pipe = Supplier::Preserving.new; start { for $*HOME { pipe.emit: .IO if .f & .ends-with('.txt'); say „Looking in ⟨{.Str}⟩ for files that end in ".txt"“ if .IO.d; .IO.dir()».&?BLOCK when .IO.d; CATCH { default { note .^name, ': ', .Str; pipe.emit: Failure.new(.item); } } } pipe.done; } react { whenever pipe.Supply { say „Checking ⟨{.Str}⟩ for "Rosetta".“; say „I found Rosetta in ⟨{.Str}⟩“ if try .open.slurp.contains('Rosetta'); LAST { say ‚Done looking for files.‘; done; } CATCH { default { note .^name, ': ', .Str; } } } whenever Promise.in(60*10) { say „I gave up to find Rosetta after 10 minutes.“; pipe.done; done; } }
Many build-in objects will return a Supply
or a Promise
. The latter will return a single value or just convey an event like a timeout. In the example above we used a Promise
in that fashion. Below we shell out to find
and process its output line by line. This could be used in a react
block if there are many different types of events to process. Here we just tap
into a stream of values and process them one by one. Since we don’t got a react
block to provide a blocking event loop, we wait for find
to finish with await
and process it’s exitcode. Anything inside the block given to .tap
will run in its own thread.
my $find = Proc::Async.new('find', $*HOME, '-iname', '*.txt'); $find.stdout.lines.tap: { say „Looking for "Rosetta" in ⟨$_⟩“; say „Found "Rosetta" in ⟨$_⟩“ if try .open.slurp.contains('Rosetta'); }; await $find.start.then: { say „find finished with exitcode: “, .result.exitcode; };
Having operators process values in parallel via threads or vector units is yet to be done. Both hyper operators and Junction
s are candidates for autothreading. If you use them today please keep in mind side effects may provide foot guns in the future.
It’s Classes All The Way Down
While building a cache for a web api that spits out JSON I found myself walking over the same data twice to fix a lack of proper typing. The JSON knows only about strings even though most of the fields are integers and timestamps. I’m fixing the types after parsing the JSON with JSON::Fast
by coercively .map
-ing .
@stations.=hyper.map: { # Here be heisendragons!
.<lastchangetime> = .<lastchangetime>
?? DateTime.new(.<lastchangetime>.subst(' ', 'T') ~ 'Z', :formatter(&ISO8601))
!! DateTime;
.<clickcount> = .<clickcount>.Int;
.<lastcheckok> = .<lastcheckok>.Int.Bool;
(note "$_/$stations-count processed" if $_ %% 1000) with $++;
.Hash
};
The hyper
helps a lot to speed things up but will put a lot of stress on the CPU cache. There must be a better way to do that.
Then lizmat showed where Rakudo shows its guts.
m: grammar A { token a { }; rule a { } }
OUTPUT: «5===SORRY!5=== Error while compiling <tmp>Package 'A' already has a regex 'a'
(did you mean to declare a multi-method?)
Tokens are regex or maybe methods. But if tokens are methods then grammars must be classes. And that allows us to subclass a grammar.
grammar WWW::Radiobrowser::JSON is JSON {
token TOP {\s* <top-array> \s* }
rule top-array { '[' ~ ']' <station-list> }
rule station-list { <station> * % ',' }
rule station { '{' ~ '}' <attribute-list> }
rule attribute-list { <attribute> * % ',' }
token year { \d+ } token month { \d ** 2 } token day { \d ** 2 } token hour { \d ** 2 } token minute { \d ** 2 } token second { \d ** 2}
token date { <year> '-' <month> '-' <day> ' ' <hour> ':' <minute> ':' <second> }
token bool { <value:true> || <value:false> }
token empty-string { '""' }
token number { <value:number> }
proto token attribute { * }
token attribute:sym<clickcount> { '"clickcount"' \s* ':' \s* '"' <number> '"' }
token attribute:sym<lastchangetime> { '"lastchangetime"' \s* ':' \s* '"' <date> '"' }
token attribute:sym<lastcheckok> { '"lastcheckok"' \s* ':' \s* '"' <bool> '"' }
}
Here we overload some tokens and forward calls to tokens that got a different name in the parent grammar. The action class follows suit.
class WWW::Radiobrowser::JSON::Actions is JSON::Actions {
method TOP($/) {
make $<top-array>.made;
}
method top-array($/) {
make $<station-list>.made.item;
}
method station-list($/) {
make $<station>.hyper.map(*.made).flat; # Here be heisendragons!
}
method station($/) {
make $<attribute-list>.made.hash.item;
}
method attribute-list($/) {
make $<attribute>».made.flat;
}
method date($_) { .make: DateTime.new(.<year>.Int, .<month>.Int, .<day>.Int, .<hour>.Int, .<minute>.Int, .<second>.Num) }
method bool($_) { .make: .<value>.made ?? Bool::True !! Bool::False }
method empty-string($_) { .make: Str }
method attribute:sym<clickcount>($/) { make 'clickcount' => $/<number>.Int; }
method attribute:sym<lastchangetime>($/) { make 'lastchangetime' => $/<date>.made; }
method attribute:sym<lastcheckok>($/) { make 'lastcheckok' => $/<bool>.made; }
}
In case you wonder how to call a method with such a funky name, use the quoting version of postfix:<.>
.
class C { method m:sym<s>{} }
C.new.'m:sym<s>'()
I truncated the examples above. The full source can be found here. The .hyper
-Version is still quite a bit faster but also heisenbuggy. In fact .hyper
may not work at all when executed to fast after a program starts or when used in a recursive Routine
. This is mostly due to the grammer being one of the oldest parts of Rakudo with the least amount of work to make it fast. That is a solvable problem. I’m looking forward to Grammar All The Things.
If you got grammars please don’t hide them. Somebody might need them to be classy.