==================== Funkce - pokračování ==================== Druhy funkcí ------------ Všechny "funkcím podobné" objekty jsou podtypy typu Code a dají se uložit do proměnných se sigilem &. * rutiny (Routine) - jde z nich vyskočit pomocí return - pojmenované: sub answer { 42 } - bezejmenné: sub($x) { $x + 42 } - další druhy: multis, metody - &?ROUTINE -- nejbližší (lexikograficky) nadřazená rutina sub add-answer($x) { if ($x ~~ Array) { $x.map(&?ROUTINE) } else { $x + 42 } } # odkazuje na &f --------------------------^ - aktuální rutinu lze též zavolat pomocí samewith: (^10).map: sub ($n) { $n <= 1 ?? 1 !! $n * samewith($n - 1) } # prvních 10 faktoriálů * bloky/lambdy (Block): { 42 }, -> $x,$y { $x+$y } - nelze z nich vyskočit pomocí return, resp. return se propaguje dál. např. sub f { my &f = -> $x { return $x }; f(42); say "hello"; } nic nevypíše, protože return ukončí nejbližší celou &f - tělo for-u je také lambda, ale např. tělo if-u ne - &?BLOCK -- nejbližší lexikograficky nadřazená lambda umožňuje psát rekurzivní lambdy: (^10).map: { $_ <= 1 ?? 1 !! $_ * &?BLOCK($_-1) } # prvních 10 faktoriálů * WhateverCode (* + 1) Multifunkce (multi dispatch, multi subs, přetěžování funkcí) ----------- Více variant funkce, stejný název, různá těla. Mohou být odlišeny: - počtem parametrů - omezeními na typy a hodnoty parametrů multi sub vypis(42) { say "odpověď" } multi sub vypis(Numeric $x) { say "nějaké číslo: $x" } multi vypis(Int $x) { say "celé číslo: $x" } # 'sub' za 'multi' jde vynechat multi vypis(Int $x where $x < 0) { say "záporné číslo: $x" } multi vypis(Int $x where $x > 0) { say "kladné číslo: $x" } multi vypis(@x) { say "Pole:"; vypis($_) for @x } multi vypis($x) { say "divná věc: {$x.perl}" } multi vypis(Str $x) { say "string: $x" } vypis([42, "pokus", [1,2], 1.1, -42, &sin]) # Pole: # odpověď # string: pokus # Pole: # celé číslo: 1 # celé číslo: 2 # nějaké číslo: 1.1 # záporné číslo: -42 # divná věc: sub sin ($) { #`(Sub+{}|45561848) ... } Z variant s vyhovujícím typovým omezením se vybere ta s "nejpřesnějším" omezením. "Přesnější" může znamenat: - parametr s omezením typu je přesnější než bez omezení - omezení pomocí where je přesnější než bez něj - odvozený typ (níže v hierarchii dědičnosti) je přesnější než nadřazený (i když nadřazený má where) - omezení konstantou (sub vypis(42)) je nejpřesnější a má vždy přednost Příklad žebříčku přesnosti: Signatura parametru: Použije se např. pro argument: 42 42 Int $x where $x>0 10 Int $x -5 Numeric $x where $x>0 2.5 Numeric $x -4.5 $x where $x>0 "20" $x "-20" Přesnost se vyhodnocuje pro každý parametr zvlášť, mohou nastat nerozhodnutelné případy: multi f( $x, Int $y) { 1 } multi f(Int $x, $y) { 2 } f(5, 6) # Ambiguous call to 'f'; these signatures all match: # :($x, Int $y) # :(Int $x, $y) Další příklady: multi fac($ where *<=1) { 1 } multi fac($n) { $n * fac($n-1) } Re-dispatch: callsame, callwith, nextsame, nextwith, samewith Vždy zavolají následující vyhovující variantu v pořadí "přesnosti". multi f($x) { say "x=$x" } multi f(42) { callsame; say "správně!" } # zavolá se stejnými parametry (42) multi f($x where $x<0) { callwith($x * 2) } # zavolá následující variantu s dvojnásobným argumentem # ( f($x*2) by udělalo nekonečnou rekurzi ) nextsame a nextwith jsou podobné, jen se nevrací: nextsame <--> return callsame nextwith($x) <--> return callwith($x) samewith (viz výše) zahájí s novými parametry nezávislý multi dispatch od začátku: multi vypis(@x) { say "Pole:"; samewith($_) for @x } Pozor: aktuálně (2017.02) nefunguje re-dispatch korektně v interaktivním módu. is cached (memoizace) --------------------- use experimental :cached; # experimentální fíčura, nutno explicitně povolit sub fib($_ where $_ >= 0) is cached { when (0|1) { $_ } default { fib($_-1) + fib($_-2) } } say F(1000) # dočkáme se Pozor: aktuálně (2017.02) není kompatibilní s multifunkcemi. Subsignatury (rozbalování polí) ------------------------------- sub vec-len([$x,$y]) { sqrt($x² + $y²) } vec-len([3,4]) # 5 (délka vektoru) > [1,2,3,4,5].rotor(2=>-1).map(-> [$x,$y] {$y-$x}) (1 1 1 1) # diference posloupnosti (rozdíly sousedních členů) Může obsahovat cokoli co normální signatura, včetně typových a where omezení, nepovinných a slurpy parametrů. sub head([$x, **@]) { $x } # První prvek sub tail([$, **@rest]) { @rest } # Vše bez prvního prvku Lze psát Haskell-like rekurzi s ukusováním prvků ze seznamu (obvykle to není dobrý nápad): multi my-sum([]) { 0 } multi my-sum([$head, **@tail]) { $head + my-sum(@tail) } my-sum([1,2,3,4,5]) # -> 15 Funkcionální červeno-černé stromy: # Zdroj: http://rosettacode.org/wiki/Pattern_matching#Perl_6 # Vysvětlení: http://blogs.perl.org/users/ovid/2013/02/red-black-trees-in-perl-6-explained.html # Původní Haskellová verze: http://rosettacode.org/wiki/Pattern_matching#Haskell enum RedBlack ; multi balance(B,[R,[R,$a,$x,$b],$y,$c],$z,$d) { [R,[B,$a,$x,$b],$y,[B,$c,$z,$d]] } multi balance(B,[R,$a,$x,[R,$b,$y,$c]],$z,$d) { [R,[B,$a,$x,$b],$y,[B,$c,$z,$d]] } multi balance(B,$a,$x,[R,[R,$b,$y,$c],$z,$d]) { [R,[B,$a,$x,$b],$y,[B,$c,$z,$d]] } multi balance(B,$a,$x,[R,$b,$y,[R,$c,$z,$d]]) { [R,[B,$a,$x,$b],$y,[B,$c,$z,$d]] } multi balance($col, $a, $x, $b) { [$col, $a, $x, $b] } multi ins( $x, @s [$col, $a, $y, $b] ) { when $x before $y { balance $col, ins($x, $a), $y, $b } when $x after $y { balance $col, $a, $y, ins($x, $b) } default { @s } } multi ins( $x, Any:U ) { [R, Any, $x, Any] } multi insert( $x, $s ) { [B, |ins($x,$s)[1..3]]; } Funkce MAIN ----------- Spustí se, pokud je soubor spuštěn přímo, ale ne pokud je načten jako modul (analogické Pythonímu if __name__ == '__main__'). Na základě signatury se automaticky parsují CLI parametry, včetně vygenerování usage. sub MAIN(Str $file, Bool :$r, Bool :$f) { ... } Po spuštění: $ . /rm.p6 Usage: rm.p6 [-r] [-f] MAIN může být multimetoda, užitečné pro více usage variant, ale také pro subcommands, jako má třeba git: multi MAIN("clone", Str $url) { say "Cloning $url..." } multi MAIN("commit") { say "Committing." } # Usage: # git.p6 clone # git.p6 commit ========= Operátory ========= Definice operátoru ------------------ sub infix:<☺>($x, $y) { $x + $y + 42 } ^ | druh operátoru: - infix -- binární, mezi operandy ($x + $y) - prefix -- unární, před operandem (- $x) - postfix -- unární, za operandem ($x++) - circumfix -- unární, kolem operandu - postcircumfix -- binární, za prvním operandem, kolem druhého($x[5]) Nastavení priority a asociativity: is tighter(&infix:<♥>) # ☺ váže těsněji než ♥ (má vyšší prioritu) # $x ☺ $y ♥ $z == ($x ☺ $y) ♥ $z looser # ☺ váže těsněji než ♥ (má nižší prioritu) # $x ☺ $y ♥ $z == $x ☺ ($y ♥ $z) equiv # ☺ váže stejně těsně jako ♥ (má stejnou prioritu) # Uzávorkování závisí na asociativitě, např. # * a / mají stejnou prioritu a levou asociativitu, # takže: 10 * 2 / 4 * 3 == ((10*2) / 4) * 3 is assoc # např. /: 36 / 2 / 3 / 4 == ((36 / 2) / 3) / 4 # např. **: 2 ** 2 ** 2 == 2 ** (2**2) # nelze napsat bez uzávorkování # porovnávací operátory: # 0 < $x < $y < 10 == 0<$x && $x<$y && $y<10 # jedno volání pro všechy operandy: # 1 ☺ 2 ☺ 3 == infix:<☺>(1,2,3) infix:<☺> je normální název funkce, dá se pomocí něj nejen definovat, ale i zavolat či získat na ni referenci: say infix:<+>(40,2) # --> 42 [4,2].map(&prefix:<->) # --> [-4, -2] Operátory mohou být multifunkce (a většina vestavěných je). Např.: multi infix:<+>(Str $a, Str $b) { $a ~ $b } "ab" + "cd" # --> "abcd" 1 + 4 # --> stále 5 Junctions (superpozice) --------- Typ Operátorový Funkční zápis zápis AND 1 & 2 & 3 all(1,2,3), all(@pole) OR 1 | 2 | 3 any(1,2,3), any XOR 1 ^ 2 ^ 3 one(1,2,3) NAND none(1,2,3) Většina operací se na junction aplikuje po prvcích: Operace Hodnota ------- ------- 0|1|2 + 10 10|11|12 sqrt (1|4|9) 1|2|3 any().uc any To platí i pro porovnávací operátory. Typ junction se projeví až při převodu na boolean (např. v podmínce if-u či when-u), který způsobí zhroucení junction. Operace Význam Hodnota Boolean ------- ------ ------- ------- $x == 0|1|42 ($x==0)|($x==1)|($x==42) False|False|True True $x ~~ Int&(*>40) ($x~~Int)&($x~~(*>40)) True&True True $x > $y&$z ($x > $y) & ($x > $z) True&False False $x eq none none($x eq "cat", $x eq "dog") none(True,False) False Metaoperátory ------------- $x ♥= $y <--> $x = $x ♥ $y $x !♥ $y <--> !($x ♥ $y) $x R♥ $y <--> $y ♥ $x Hyperoperátory: aplikují operátor rekurzivně na prvky seznamu [1, 2, 3] >>+<< [10,20,30] eqv [11,22,33] # na obou stranách musí být stejně dlouhé seznamy [1, 2, 3] >>+>> [1] eqv [2,3,4] # seznam na pravé straně se natáhne zopakováním # (analogicky <<+<<) # pomůcka: šipka směřuje ke kratšímu seznamu # (jako by menšítka porovnávala délky) [1, 2, 3] >>+>> 1 eqv [2,3,4] # skalár se zopakuje jako 1prvkový seznam [0, 10] <<+>> [1,2,3,4] eqv [1,12,3,14] # automaticky natáhne kratší ze seznamů [[1,2],[3,4]] >>+<< [[1,0],[0,1]] eqv [[2,2],[3,5]] # rekurzivně zalézá do podseznamů # (např. sčítání matic) [1,-2,-3]>>.abs eqv [1,2,3] # hypercall, jako .map(*.abs) [[1,-2],-3]>>.abs eqv [[1,2],3] # (ale narozdíl od map-u rekurzivní) Zip: [1,2,3] Z+ [10,20,30] eqv [11,22,33] # jako >>+<<, ale nezalézá do podseznamů [1,2,3] Z [10,20,30] eqv [(1,10), (2,20), (3,30)] # samotné Z tvoří dvojice (jako Z,) @a[1..*] Z- @a # diference posloupnosti trochu jinak Kartézský součin: [0,10] X+ [0,1] eqv [0,1,10,11] [1,2] X~ eqv <1a 1b 2a 2b> [1,2] X [3,4] eqv ((1,3), (1,4), (2,3), (2,4)) # jako X, Redukce: [+] (1,2,3) == 6 # 1 + 2 + 3 [*] 1..5 == 120 # 5! [+] () == 0 # každý operátor má svůj neutrální prvek [*] () == 1 [\+] (1,2,3,4) == (1, 3, 6, 10) # trojúhelníková redukce (prefixové součty) [\*] (1..5) == (1,2,6,24,120) # prvních 5 faktoriálů Další příklady: [X~] ([^2] xx 4) # všechny 4ciferné binární řetězce v lexikograf. pořadí [>>+<<] lines>>.words # posčítá vstup po sloupečcích (příklad z letáčku)