Interakce s cizím kódem ======================= ## NativeCall (volání Cčkových funkcí) ## -> https://docs.perl6.org/language/nativecall use NativeCall; sub puts(Str --> int) is native {*} <-- hledá symbol mezi načtenými knihovnami puts("Hello world") # --> 12 (zde z libc) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ class PwStruct is repr('CStruct') { has Str $.pw_name; has Str $.pw_passwd; has uint32 $.pw_uid; has uint32 $.pw_gid; has Str $.pw_gecos; has Str $.pw_dir; has Str $.pw_shell; } sub getuid() returns uint32 is native { * }; sub getpwuid(uint32 $uid) returns PwStruct is native { * }; say "User full name: " ~ getpwuid(getuid()).pw_gecos; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ sub curl_easy_init(--> Pointer) is native {*} # Knihovna se automaticky přilinkuje za běhu ^^ sub curl_easy_setopt_s(Pointer, uint32, Str --> int32) is native is symbol {*} # Název Cčkové funkce odlišný od perlové ^^^ # Parametr typu callback -- překladač automaticky vygeneruje Cčkový wrapper, # který zavolá perlovou funkci. sub curl_easy_setopt_cb(Pointer, uint32, & (CArray[uint8], uint64, uint64, Pointer) --> int32) is native is symbol {*} sub curl_easy_perform(Pointer --> int32) is native {*} constant CURLOPT_WRITEFUNCTION = 20011; constant CURLOPT_URL = 10002; sub write-cb($data, $size, $nmemb, $) { my $buf = Buf.new( $data[^($size*$nmemb)] ); say "Received: " ~ $buf.decode('utf-8'); } my $curl = curl_easy_init(); curl_easy_setopt_s($curl, CURLOPT_URL, "http://mj.ucw.cz/vyuka/1617/p6/"); curl_easy_setopt_cb($curl, CURLOPT_WRITEFUNCTION, &write-cb); curl_easy_perform($curl); ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ## Inline::Perl5 ## -> https://github.com/niner/Inline-Perl5 # DBI - databázové rozhraní use Inline::Perl5; use DBI:from; my $dbh = DBI.connect('dbi:SQLite:dbname=db.sqlite'); my @products := $dbh.selectall_arrayref( 'select * from products', {Slice => {}} ); # Dancer - webový framework use Dancer:from; get '/roll' => sub { (1..6).roll } <--- callback z P5 do P6 dance; Generování kódu v době importu ============================== -> https://archive.fosdem.org/2015/schedule/event/perl6_beyond_dynamic_vs_static/ Metoda EXPORT dává jemnější kontrolu nad exportovanými symboly, například je může generovat dynamicky: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Shell/AsSub.pm6 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ sub EXPORT(*@commands) { # zavolá se automaticky při importu s parametry z use my %subs; # vrací seznam exportovaných symbolů for @commands -> $command { %subs{'&' ~ $command} = sub (*@args) { run $command, |@args; } } return %subs; } ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Import (a tedy i vygenerování funkcí) proběhne v čase překladu. use Shell::AsSub ; mkdir '-p', '/tmp/pokus'; touch '/tmp/pokus/test'; rm |<-f /tmp/pokus/test>; rmdir '/tmp/pokus'; # funkce neexistuje -> compile-time(!!) chyba Soubory ======= my $pwd = slurp "/etc/passwd"; <-- vysrkne celý obsah souboru spurt "passwd.bak", $pwd; <-- vystříkne obsah do souboru copy "passwd", "passwd.bak" <-- zkopíruje obsah souboru ## IO::Path ## Pamatuje si cestu k souboru, umí ji opracovávat (rozkládat na komponenty, přidávat přípony, relativizovat apod.) a také na ni provádět I/O: "jméno".IO.slurp <-- vytvoří IO::Path a zavolá na ni slurp "jméno".IO.spurt($obsah); <-- je jasné, co je podmět a co předmět for "log".IO.lines { ... } <-- cyklus přes řádky souboru for "/etc".IO.dir { ... } <-- cyklus přes obsah adresáře "jméno".IO.e <-- vrátí True, pokud cesta existuje "jméno".IO.d <-- ... a je to adresář "staré".IO.rename('nové') <-- přejmenuje adresářovou položku ## IO::Handle ## my $w = "jméno".IO.open(:w); <-- otevře soubor pro zápis my $r = open("jméno", :r); <-- lze též volat jako subrutinu my $b = open("jméno", :r, :bin); <-- binární soubor $w.say("řádek"); <-- zapíše řádek do souboru $w.close; <-- uzavře soubor $r.get <-- přečte další řádek $r.read(64) --> Blob <-- přečte blok bytů Líné seznamy ------------ Následující konstrukce zpracuje řádky souboru, aniž by se všechny ukládaly do paměti: for "slovník".IO.lines.grep(rx{brundibár}) { ... } my $seq = $io.lines; <-- vyrobí Seq: líně vyhodnocovaný jednou iterovatelný seznam my $s2 = $seq.grep; <-- vrátí jinou Seq (podobně map apod.) my $l = $s2.cached; <-- vyrobí List: líně vyhodnocovaný seznam, který si pamatuje už vygenerované hodnoty a je možné ho indexovat my @a = $io.lines.lazy; <-- vyrobí Array: seznam skalárních kontejnerů, i ten je líně vyhodnocovaný my @b = eager @a; <-- vynutí "pilné" vyhodnocení # Generátor Seq: pokaždé, když po Seq někdo chce další hodnotu, blok # popoběhne až do příštího take. (Vlastně je to korutina.) my $seq = gather { for ^100 { take $_*$_ } <-- take je dynamically scoped }; Paralelismus ============ K dispozici jsou vlákna, zámky, monitory a podobné low-level konstrukce. Mnohem zajímavější jsou různé high-level koncepty... Paralelní sekvence ------------------ (1..100).hyper <-- vyrobí HyperSeq, která se v některých kontextech (třeba for) iteruje paralelně .hyper(:batch(1000), :degree(10)) <-- 10 vláken, bloky po 1000 prvcích .race(...) <-- totéž, ale nedodržuje pořadí Spouštění procesů ----------------- run 'git', 'status'; <-- spustí proces (v sink kontextu výjimka při chybě) shell 'ls -l | sort >x'; <-- interpretuje příkaz shellem my $proc = run 'status', :out; <-- vyrobí objekt typu Proc for $proc.out.lines { .say } <-- projde výstup programu $proc.out.close; # Konstrukce pipeline jako v shellu: my $a = run 'cat', :in, :out; my $b = run 'sha1sum', :in($a.out), :out; $a.in.say('Ahoy!'); $a.in.close; say $b.out.slurp-rest; $b.out.close; Asynchronní procesy ------------------- my $log = Proc::Async.new(< tail -f /var/log/syslog >); $log.stdout.tap({ .say }) <-- $log.stdout je Supply (viz níže) my $done = $log.start; <-- $done je Promise (viz níže) sleep 10; $log.kill('TERM'); try await $done; <-- bez try vyvolá výjimku Promises -------- Slib dodat v budoucnosti výsledek paralelně běžícího výpočtu. Slib je možné časem splnit (a předat výsledek), nebo porušit (a předat výjimku). my $p = Promise.new; say $p.status; <-- "Planned" $p.keep('okay'); <-- [nebo $p.break('oh my')] say $p.status; <-- "Kept" [nebo "Broken"] say $p.result; <-- "okay" [nebo výjimka; lze použít $p.cause] my $p = start { 42; } <-- spustíme paralelní výpočet, získáme promise my $res = await $p; <-- počká na dokončení a pak vrátí výsledek (lze čekat na více promises najednou) my $q = Promise.in(10); <-- promise, která bude splněna za 10 sekund my $r = $q.then({ say "Done" }) <-- promise, která se spustí po splnění jiné await Promise.allof($p, $r); <-- promise, která se splní po splnění všech await Promise.anyof($p, $r); <-- promise, která se splní po splnění libovolné Channels -------- Fronta s více paralelními producenty i konzumenty. my $ch = Channel.new; $ch.send(1); say $ch.receive; start { $ch.send($_) for ^10; $ch.close; } say $ch.list; # Smyčka zpracovávající položky z jednoho nebo více kanálů # (skončí, až se uzavřou všechny kanály, případně příkazem "done") react { whenever $ch -> $item { say $item } } Supplies -------- Proud dat, který lze odebírat vícekrát (příklad: distribuce událostí). # Live supply: všichni přijímají tentýž proud dat # (kdo se připojí později, stará data už nedostane) my $splr = Supplier.new; <-- generátor my $sup = $splr.Supply; <-- konkrétní supply $sup.tap( -> $x { say $x } ); <-- "zabodneme pípu" $splr.emit($_) for ^10; <-- druhá strana vysílá prvky # On-demand supply: každý přijimá svou instanci proudu dat (od začátku) my $sup = supply { emit($_) for ^10 } $sup.tap({ say "First: $_" }); $sup.tap({ say "Second: $_" }); # Též lze použít react/whenever react { whenever Supply.interval(1) { <-- generuje prvky po sekundě .say; done when 4; LAST { ... } <-- na konec lze reagovat phaserem } } # Supply je vlastně duální k Seq (u Supply má iniciativu odesilatel) my $sup = supply { emit($_) for ^100; } my $div7 = $sup.grep({ $_ %% 7 }); <-- vznikne další supply say $div7.list; Síťová komunikace ----------------- # Jednoduchý chatovací TCP server my %clients := SetHash.new; react { # listen() vrací Supply, jehož položky jsou nově příchozí spojení whenever IO::Socket::Async.listen('127.0.0.1', 3333) -> $conn { %clients{$conn} = True; # .Supply dává data po nepravidelných blocích # .lines rozděluje do řádek (a zároveň slepuje řádky přes hranici # bloku), výsledkem nový supply vracející jednotlivé řádky whenever $conn.Supply.lines -> $line { for (%clients.keys) { .print($line~"\n"); } LAST { %clients{$conn} :delete; } # klient zavřel spojení } } }