Obsah
1. Předání parametrů do funkcí hodnotou
2. Předání parametrů referencí
3. Změna hodnoty proměnné předané referencí
4. Měnitelný parametr předaný hodnotou
5. Iterátory – malé zopakování z předchozích dílů
7. Průchod sekvencí se získáním indexu každého prvku
9. Funkcionální přístup při zpracování sekvencí – funkce vyššího řádu
10. Metoda map: aplikace vybrané funkce na každý prvek sekvence
11. Metoda filter: výběr pouze některých prvků s použitím filtru
12. Metody take a take_while: získání pouze zvoleného počtu prvků ze sekvence
13. Nekonečné sekvence a práce s nimi
14. Metoda fold: postupné zpracování prvků sekvence
15. Repositář s demonstračními příklady
1. Předání parametrů do funkcí hodnotou
První problematikou, kterou se dnes budeme zabývat, je způsob předávání parametrů funkcím přes takzvané reference. Již na tomto místě je vhodné upozornit na to, že pod názvem reference se v programovacím jazyku Rust skrývá koncept, který je v některých ohledech odlišný od referencí známých z Javy či z C++. Nejprve si však připomeňme, jak vypadá funkce, do které se předávají parametry hodnotou (value). Taková funkce může vypadat následovně:
fn print_values(arg1:i32, arg2:i32) { println!("function print_values()"); println!("Variable1: {}", arg1); println!("Variable2: {}", arg2); }
Při volání této funkce jí můžeme předat jak proměnné, tak i hodnoty. U proměnných nezávisí na tom, zda jsou měnitelné či neměnitelné, ovšem musí mít správný typ, což je striktně hlídáno:
fn main() { let variable1 = 1; let mut variable2 = 1; print_values(variable1, variable2); print_values(100, 200); }
2. Předání parametrů referencí
V některých případech však není nutné, aby se parametr funkci musel předávat hodnotou, protože předání hodnotou vlastně znamená, že se vytvoří kopie parametru, což je mnohdy zbytečné (můžeme například chtít předat rozsáhlou strukturu). V takovém případě lze předat pouze referenci na hodnotu. Datový typ „reference na hodnotu typu TYP_X“ se zapisuje &TYP_X. Funkce akceptující referenci má z tohoto důvodu nepatrně odlišnou deklaraci datových typů parametrů. Uvnitř funkce je nutné reference takzvaně dereferencovat, na což se používá unární operátor * zapisovaný před argument či proměnnou typu reference:
fn pass_by_reference(arg1:&i32, arg2:&i32) { println!("function pass_by_reference()"); println!("Variable1: {}", *arg1); println!("Variable2: {}", *arg2); }
Funkce se bude volat následovně (povšimněte si použití & pro získání reference na proměnné):
fn main() { let variable1 = 1; let mut variable2 = 2; print_variables(variable1, variable2); pass_by_reference(&variable1, &variable2); }
Již zde můžeme vidět odlišnost od referencí používaných v Javě i C++. Javu si dovolím z dalšího popisu vynechat, protože v ní se striktně rozlišuje mezi primitivními datovými typy (předávané vždy hodnotou) a referenčními (objektovými) typy (předávané naopak vždy referencí); což mj. vede ke zjednodušení syntaxe a k některým sémantickým problémům. V C++ je možné použít skutečné reference, ovšem s nimi se uvnitř funkce pracuje odlišně – nemusí se například provádět explicitní dereferencování. Reference jsou v obou jazycích vždy inicializované. Naopak v C++ není možné použít následující zápis, který je naopak v Rustu zcela legální:
pass_by_reference(&100, &200);
Celý zdrojový kód příkladu, v němž je deklarována funkce akceptující parametry předávané hodnotou a funkce akceptující reference, vypadá následovně:
fn print_variables(arg1:i32, arg2:i32) { println!("function print_variables()"); println!("Variable1: {}", arg1); println!("Variable2: {}", arg2); } fn pass_by_reference(arg1:&i32, arg2:&i32) { println!("function pass_by_reference()"); println!("Variable1: {}", *arg1); println!("Variable2: {}", *arg2); } fn main() { let variable1 = 1; let mut variable2 = 2; print_variables(variable1, variable2); pass_by_reference(&variable1, &variable2); }
Po spuštění by se měly dvakrát po sobě vypsat shodné hodnoty:
function print_variables() Variable1: 1 Variable2: 2 function pass_by_reference() Variable1: 1 Variable2: 2
Ve skutečnosti není nutné dereferenci zapisovat ve chvíli, kdy si překladač dokáže odvodit, že přistupujeme k referencované hodnotě. Stručně řečeno – pokud z funkce pass_by_reference odstraníte hvězdičky (operátor dereference), proběhne překlad v pořádku. Totéž platí i při volání funkce, kdy je možné zapsat znak & kolikrát chcete. Je to zvláštní a užitečný důsledek silné typové kontroly a typové inference.
3. Změna hodnoty proměnné předané referencí
Proměnné, které jsou do nějaké funkce předané referencí, lze uvnitř této funkce změnit, ovšem je nutné dodržet několik podmínek. Nejprve se podívejme na demonstrační příklad, který vychází z částečně mylné domněnky, že se s referencemi v Rustu zachází stejně jako s ukazateli (zápis dereference se skutečně práci s ukazateli podobá, ale jen syntakticky, nikoli sémanticky). V příkladu je deklarována funkce nazvaná mutate_variable, v níž se přes referenci mění proměnná předaná v argumentu arg2:
fn mutate_variable(arg1:i32, arg2:&i32) { println!("mutation ..."); let x = arg1 * 2; *arg2 = x; } fn print_variables(arg1:i32, arg2:i32) { println!("function print_variables()"); println!("Variable1: {}", arg1); println!("Variable2: {}", arg2); } fn pass_by_reference(arg1:&i32, arg2:&i32) { println!("function pass_by_reference()"); println!("Variable1: {}", *arg1); println!("Variable2: {}", *arg2); } fn main() { let variable1 = 1; let mut variable2 = 1; print_variables(variable1, variable2); pass_by_reference(&variable1, &variable2); mutate_variable(variable1, &variable2); print_variables(variable1, variable2); pass_by_reference(&variable1, &variable2); }
Tento příklad se ovšem nepodaří přeložit, a to z toho důvodu, že volané funkci sice skutečně předáváme měnitelnou proměnnou, ovšem typ &i32 označuje neměnitelnou referenci:
error: cannot assign to immutable borrowed content `*arg2` --> 29_func_params.rs:4:5 | 4 | *arg2 = x; | ^^^^^^^^^ error: aborting due to previous error
Pokud skutečně potřebujeme změnit hodnotu proměnné předané referencí, je nutné pozměnit typ druhého parametru funkce na &mut i32, což je typ znamenající „referenci, přes níž je možné změnit hodnotu referencovaného objektu“:
fn mutate_variable(arg1:i32, arg2:&mut i32) { println!("mutation ..."); let x = arg1 * 2; *arg2 = x; } fn print_variables(arg1:i32, arg2:i32) { println!("function print_variables()"); println!("Variable1: {}", arg1); println!("Variable2: {}", arg2); } fn pass_by_reference(arg1:&i32, arg2:&i32) { println!("function pass_by_reference()"); println!("Variable1: {}", *arg1); println!("Variable2: {}", *arg2); } fn main() { let variable1 = 1; let mut variable2 = 1; print_variables(variable1, variable2); pass_by_reference(&variable1, &variable2); mutate_variable(variable1, &mut variable2); print_variables(variable1, variable2); pass_by_reference(&variable1, &variable2); }
Po této nepatrné změně se program přeloží a bude pracovat podle požadavků – funkce mutate_variable skutečně změní hodnotu druhé proměnné:
function print_variables() Variable1: 1 Variable2: 1 function pass_by_reference() Variable1: 1 Variable2: 1 mutation ... function print_variables() Variable1: 1 Variable2: 2 function pass_by_reference() Variable1: 1 Variable2: 2
4. Měnitelný parametr předaný hodnotou
To, že je reference měnitelná, však ještě neznamená, že budeme moci změnit hodnotu neměnitelné (immutable) proměnné. Překladač jazyka Rust si tuto skutečnost ohlídá. Pokud například změníme deklaraci druhé proměnné tak, že se bude jednat o proměnnou neměnitelnou:
let variable1 = 1; let variable2 = 1;
Dojde při překladu k chybě:
error: cannot borrow immutable local variable `variable2` as mutable --> 29_func_params.rs:26:37 | 21 | let variable2 = 1; | --------- use `mut variable2` here to make mutable ... 26 | mutate_variable(variable1, &mut variable2); | ^^^^^^^^^ cannot borrow mutably error: aborting due to previous error
Nepomůže nám ani pokus o obelstění překladače tím, že při volání funkce vynecháme klíčové slovo mut:
let variable1 = 1; let variable2 = 1; mutate_variable(variable1, &variable2);
error[E0308]: mismatched types --> 29_func_params.rs:26:32 | 26 | mutate_variable(variable1, &variable2); | ^^^^^^^^^^ values differ in mutability | = note: expected type `&mut i32` = note: found type `&i32` error: aborting due to previous error
Podívejme se ještě na jedno úskalí jazyka Rust. Pokud se pokusíme změnit hodnotu argumentu uvnitř funkce:
fn mutate_variable(arg1:i32, arg2:&mut i32) { println!("mutation ..."); let x = arg1 * 2; arg1 = x; *arg2 = arg1; }
Dojde opět k detekci chyby, protože argument je neměnitelný:
error[E0384]: re-assignment of immutable variable `arg1` --> 30_func_params.rs:4:5 | 1 | fn mutate_variable(arg1:i32, arg2:&mut i32) { | ---- first assignment to `arg1` ... 4 | arg1 = x; | ^^^^^^^^^ re-assignment of immutable variable error: aborting due to previous error
Tuto chybu můžeme napravit použitím klíčového slova mut před jméno argumentu. Tím ovšem jen povolujeme změnu hodnoty, která vznikla při volání funkce, nikoli změnu původní proměnné (první parametr je předáván hodnotou):
fn mutate_variable(mut arg1:i32, arg2:&mut i32) { println!("mutation ..."); let x = arg1 * 2; arg1 = x; *arg2 = arg1; } fn print_variables(arg1:i32, arg2:i32) { println!("function print_variables()"); println!("Variable1: {}", arg1); println!("Variable2: {}", arg2); } fn pass_by_reference(arg1:&i32, arg2:&i32) { println!("function pass_by_reference()"); println!("Variable1: {}", *arg1); println!("Variable2: {}", *arg2); } fn main() { let variable1 = 1; let mut variable2 = 1; print_variables(variable1, variable2); pass_by_reference(&variable1, &variable2); mutate_variable(variable1, &mut variable2); print_variables(variable1, variable2); pass_by_reference(&variable1, &variable2); }
Po spuštění je patrné, že se změna prvního argumentu nijak neprojevila vně funkce:
function print_variables() Variable1: 1 Variable2: 1 function pass_by_reference() Variable1: 1 Variable2: 1 mutation ... function print_variables() Variable1: 1 Variable2: 2 function pass_by_reference() Variable1: 1 Variable2: 2
5. Iterátory – malé zopakování z předchozích dílů
Minule jsme se seznámili s objektem typu range, který dokáže generovat určitou sekvenci hodnot. Tento typ objektů je specifickým typem takzvaných iterátorů. Iterátory jsou známé a často používané i v dalších programovacích jazycích; v Rustu je jejich užitečnost ještě zvýšena tím, že iterátory lze použít pro vytváření a zpracování takzvaných „líných sekvencí“, které mohou být v některých případech i nekonečné. Zpracování (potenciálně) nekonečných sekvencí v konečném čase je umožněno právě díky tomu, že se prvky sekvence vyhodnocují „líně“, tj. až v tom okamžiku, kdy jsou skutečně zapotřebí.
V dalších příkladech budeme pro jednoduchost často používat objekty typu range, takže si jen připomeňme, jak se s nimi pracuje:
fn main() { // projiti sekvence hodnot od 1 do 10 (vcetne) for i in 1..11 { println!("{}", i); } }
6. Délka sekvence
Délku sekvence můžeme zjistit několika způsoby. Nejprimitivnější je průchod všemi prvky sekvence v programové smyčce typu for each:
fn main() { // promenna pouzita pro vypocet delky sekvence (naivni implementace) let mut cnt :u32 = 0; // projiti sekvence hodnot od 1 do 10 (vcetne) for i in 1..11 { cnt += 1; } println!("{}", cnt); }
Tuto možnost si zde ukazujeme jen z toho důvodu, že namísto jména řídicí proměnné smyčky můžeme použít univerzální vzorek _, což je idiomatický způsob použitý ve chvíli, kdy programátor potřebuje naznačit, že ho hodnota přiřazovaná do řídicí proměnné nezajímá:
fn main() { // promenna pouzita pro vypocet delky sekvence (naivni implementace) let mut cnt :u32 = 0; // projiti sekvence hodnot od 1 do 10 (vcetne) // ridici promenna se nikdy nepouzije for _ in 1..11 { cnt += 1; } println!("{}", cnt); }
Iterátor je samostatným objektem, což již víme z minula, takže ho lze přiřadit do proměnné:
fn main() { // iterator (povsimnete si, ze je nemenny) let iter = 1..11; // promenna pouzita pro vypocet delky sekvence (naivni implementace) let mut cnt :u32 = 0; // projiti sekvence hodnot od 1 do 10 (vcetne) // ridici promenna se nikdy nepouzije for _ in iter { cnt += 1; } println!("{}", cnt); }
Mnohem jednodušší a samozřejmě i rychlejší je však zavolat metodu count vracející počet prvků (pokud se tedy nejedná o nekonečnou sekvenci, tam je ovšem volání této metody ohlídáno):
fn main() { // iterator (povsimnete si, ze je nemenny) let iter = 1..11; // promenna pouzita pro vypocet delky sekvence (typ musi byt nastaven na usize) let cnt :usize; cnt = iter.count(); println!("count = {}", cnt); }
7. Průchod sekvencí se získáním indexu každého prvku
V mnoha případech může být užitečné získat při průchodu sekvence indexy jednotlivých prvků. První řešení spočívá v použití počítané programové smyčky, to je však poměrně nešikovné (viz Java a její rozhraní ListIterator). Lepší je následující způsob založený na metodě enumerate, která vrací nový iterátor. Tento iterátor generuje dvojici index+prvek a jeho použití je velmi jednoduché:
fn main() { // iterator (povsimnete si, ze je nemenny) let iter = 1..11; // pruchod sekvenci se ziskanim indexu a hodnoty kazdeho prvku for (index,item) in iter.enumerate() { println!("item[{}] = {}", index, item); } }
Můžeme zde vidět i novou sémantiku (index,item) použitelnou i v jiných částech jazyka.
8. Otočení sekvence
V některých případech je zapotřebí sekvenci otočit. K tomuto účelu lze využít metodu nazvanou rev (od slova „reverse“). Její použití je jednoduché, jak je ostatně patrné z následujícího demonstračního příkladu:
fn main() { // iterator (povsimnete si, ze je nemenny) let iter1 = 1..11; let iter2 = iter1.rev(); // pruchod sekvenci se ziskanim indexu a hodnoty kazdeho prvku for (index,item) in iter2.enumerate() { println!("item[{}] = {}", index, item); } }
Výsledek by nás v tomto případě neměl překvapit:
item[0] = 10 item[1] = 9 item[2] = 8 item[3] = 7 item[4] = 6 item[5] = 5 item[6] = 4 item[7] = 3 item[8] = 2 item[9] = 1
O některých důsledcích plynoucích z použití této metody si více řekneme v části o nekonečných sekvencích.
9. Funkcionální přístup při zpracování sekvencí – funkce vyššího řádu
V programovacím jazyce Rust nalezneme několik metod použitelných při zpracování sekvencí vytvářených pomocí iterátorů. Tyto metody mají jména i sémantiku převzatou z funkcionálních jazyků (poprvé se objevily v LISPu, který sice není čistě funkcionální, ale nalezneme v něm mnoho prvků, které se do FP jazyků posléze dostaly). Mezi zmíněné metody – které by byly ve funkcionálních jazycích implementovány funkcemi vyššího řádu – patří zejména map, filter, take, take_while a fold (někde se nazývá reduce).
10. Metoda map: aplikace vybrané funkce na každý prvek sekvence
S funkcí či možná přesněji řečeno s metodou vyššího řádu nazvanou map jsme se již setkali minule, takže si jen krátce připomeňme, že map postupně aplikuje jí předanou funkci na jednotlivé prvky nějaké sekvence a vytváří tak sekvenci novou (modifikovanou). V prvním příkladu se funkce map bude postupně volat neanonymní funkci square:
fn square(x :i32) -> i32 { x * x } fn main() { // iterator (povsimnete si, ze je nemenny) let iter1 = 1..11; let iter2 = iter1.map(square); // pruchod sekvenci se ziskanim indexu a hodnoty kazdeho prvku for (index,item) in iter2.enumerate() { println!("item[{}] = {}", index, item); } }
Po překladu získáme na standardním výstupu tyto zprávy:
item[0] = 1 item[1] = 4 item[2] = 9 item[3] = 16 item[4] = 25 item[5] = 36 item[6] = 49 item[7] = 64 item[8] = 81 item[9] = 100
Můžeme samozřejmě použít i anonymní funkci, což je více idiomatické:
fn main() { // iterator (povsimnete si, ze je nemenny) let iter1 = 1..11; let iter2 = iter1.map(|x| x*x); // pruchod sekvenci se ziskanim indexu a hodnoty kazdeho prvku for (index,item) in iter2.enumerate() { println!("item[{}] = {}", index, item); } }
Jen si připomeňme, že do anonymní funkce lze bez problémů zapisovat bloky, protože poslední výraz v bloku určuje i hodnotu celého bloku (blok je výraz, ne příkaz). To stejné platí i pro rozhodovací konstrukce if-then či match, takže následující zápis je zcela korektní a funkční:
fn main() { // iterator (povsimnete si, ze je nemenny) let iter1 = 1..11; let iter2 = iter1.map(|x| {if x<5 {x*x} else {10/x}}); // pruchod sekvenci se ziskanim indexu a hodnoty kazdeho prvku for (index,item) in iter2.enumerate() { println!("item[{}] = {}", index, item); } }
Po spuštění takto upraveného příkladu získáme na standardním výstupu těchto deset řádků:
item[0] = 1 item[1] = 4 item[2] = 9 item[3] = 16 item[4] = 2 item[5] = 1 item[6] = 1 item[7] = 1 item[8] = 1 item[9] = 1
11. Metoda filter: výběr pouze některých prvků s použitím filtru
Užitečná je v mnoha případech i metoda nazvaná filter, která ze sekvence vybere pouze ty prvky, které odpovídají zadanému predikátu. Predikátem je libovolná funkce vracející pravdivostní hodnotu true či false. Zkusme vytvořit a použít predikát, kterým bude funkce testující, zda je parametr sudé číslo. Povšimněte si však jedné maličkosti – anonymní funkci uvedené v metodě filter se postupně předává reference na zpracovávaný prvek, tudíž je nutné před voláním našeho predikátu even použít dereferenci *x:
fn even(x :u32) -> bool { x % 2 == 0 } fn main() { // iterator (povsimnete si, ze je nemenny) let iter1 = 1..11; let iter2 = iter1.filter(|x| even(*x)); // pruchod sekvenci se ziskanim indexu a hodnoty kazdeho prvku for (index,item) in iter2.enumerate() { println!("item[{}] = {}", index, item); } }
Výsledek je předvídatelný:
item[0] = 2 item[1] = 4 item[2] = 6 item[3] = 8 item[4] = 10
Zajímavé je, že anonymní funkci volající náš predikát lze zapsat i jinak – již parametr anonymní funkce je označen jako reference, takže kód je čitelnější (dereferenci si překladač doplní automaticky):
fn even(x :u32) -> bool { x % 2 == 0 } fn main() { // iterator (povsimnete si, ze je nemenny) let iter1 = 1..11; let iter2 = iter1.filter(|&x| even(x)); // pruchod sekvenci se ziskanim indexu a hodnoty kazdeho prvku for (index,item) in iter2.enumerate() { println!("item[{}] = {}", index, item); } }
Výsledek překladu bude (bitově) naprosto shodný, stejně jako bude stejný obsah standardního výstupu:
item[0] = 2 item[1] = 4 item[2] = 6 item[3] = 8 item[4] = 10
Nejlepší je samozřejmě vložit predikát přímo do anonymní funkce předané metodě filter. S následujícím kódem se pravděpodobně setkáte nejčastěji:
fn main() { // iterator (povsimnete si, ze je nemenny) let iter1 = 1..11; let iter2 = iter1.filter(|x| x % 2 == 0); // pruchod sekvenci se ziskanim indexu a hodnoty kazdeho prvku for (index,item) in iter2.enumerate() { println!("item[{}] = {}", index, item); } }
Výsledek je podle očekávání stále stejný:
item[0] = 2 item[1] = 4 item[2] = 6 item[3] = 8 item[4] = 10
12. Metody take a take_while: získání pouze zvoleného počtu prvků ze sekvence
Ve chvíli, kdy je nutné z nějaké sekvence získat pouze určitý počet prvků, je možné zavolat metodu nazvanou take. Tato metoda vrátí pouze prvních n prvků ze sekvence, nezávisle na tom, zda se jedná o sekvenci konečnou či nekonečnou. V následujícím programu se nejprve vytvoří objekt typu range se sto prvky, následně se s využitím filtru vyberou pouze sudé prvky a v posledním kroku se z vyfiltrovaných prvků (je jich celkem padesát) vytvoří sekvence o pouhých deseti prvcích:
fn main() { // iterator (povsimnete si, ze je nemenny) let iter1 = 1..101; let iter2 = iter1.filter(|x| x % 2 == 0); let iter3 = iter2.take(10); // pruchod sekvenci se ziskanim indexu a hodnoty kazdeho prvku for (index,item) in iter3.enumerate() { println!("item[{}] = {}", index, item); } }
Po překladu a spouštění se na standardní výstup vypíšou následující zprávy:
item[0] = 2 item[1] = 4 item[2] = 6 item[3] = 8 item[4] = 10 item[5] = 12 item[6] = 14 item[7] = 16 item[8] = 18 item[9] = 20
Funkce/metoda take bude pracovat správně i ve chvíli, kdy požadujeme větší počet prvků, než máme k dispozici. K žádnému překročení mezí nedojde:
fn main() { // iterator (povsimnete si, ze je nemenny) let iter1 = 1..101; let iter2 = iter1.filter(|x| x % 2 == 0); let iter3 = iter2.take(1000); // pruchod sekvenci se ziskanim indexu a hodnoty kazdeho prvku for (index,item) in iter3.enumerate() { println!("item[{}] = {}", index, item); } }
item[0] = 2 item[1] = 4 item[2] = 6 item[3] = 8 item[4] = 10 item[5] = 12 item[6] = 14 ... ... ... item[41] = 84 item[42] = 86 item[43] = 88 item[44] = 90 item[45] = 92 item[46] = 94 item[47] = 96 item[48] = 98 item[49] = 100
Další zajímavou a užitečnou funkcí (metodou?) vyššího řádu, kterou lze použít pro zpracování sekvencí, je funkce take-while. Tato funkce se částečně podobá funkci take, která z předané sekvence získá prvních n prvků, ovšem funkce take-while namísto hodnoty n očekává predikát. Dokud predikát vrací hodnotu true, budou se prvky ze vstupní sekvence předávat do sekvence výstupní, při první hodnotě false je funkce ukončena. Podívejme se na jednoduchý příklad:
fn main() { let iter1 = 1..999; let iter2 = iter1.filter(|x| x % 2 == 0); let iter3 = iter2.take_while(|x| x*x < 200); // pruchod sekvenci se ziskanim indexu a hodnoty kazdeho prvku for (index,item) in iter3.enumerate() { println!("item[{}] = {}", index, item); } }
item[0] = 2 item[1] = 4 item[2] = 6 item[3] = 8 item[4] = 10 item[5] = 12 item[6] = 14
13. Nekonečné sekvence a práce s nimi
V některých funkcionálních jazycích se setkáme s termíny nekonečná sekvence popř. „líná“ sekvence (lazy sekvence). Tyto termíny spolu úzce souvisí. Připomeňme si, že sekvencí je v programovacím jazyku Rust myšlen objekt typu „iterátor“, v němž je implementována metoda next, po jejímž zavolání se buď vrátí další prvek nebo se, pokud další prvek neexistuje, vrátí hodnota None (ve skutečnosti metoda next vrací hodnotu typu Option<?>, což však bude téma probírané příště). Pokud iterátor postupně vrací prvky z nějaké sekvence, znamená to, že po vrácení posledního prvku je sekvence ukončena. Ovšem může také existovat iterátor – a je to poměrně časté – který neustále vrací další a další hodnoty. Jeden takový iterátor můžeme získat snadno, protože i objekt typu range nemusí mít uvedenou horní mez. Zápis je sice poněkud zvláštní, ale plně funkční:
fn main() { let iter1 = 1..; // pruchod sekvenci se ziskanim indexu a hodnoty kazdeho prvku for (index,item) in iter1.enumerate() { println!("item[{}] = {}", index, item); } }
Sami si vyzkoušejte příklad přeložit a spustit. Program se ukončí pomocí Ctrl+C nebo oblíbeným příkazem kill.
Nekonečné sekvence již tedy dokážeme vytvořit, ale co se s nimi dá dělat, když při pokusu o jejich zpracování dostaneme nekonečný počet hodnot? Ve skutečnosti tak tomu být nemusí, protože metody, které se sekvencemi pracují (map, filter atd.), ve skutečnosti nemusí volat jim předané (anonymní) funkce ihned, ale až ve chvíli, kdy je skutečně nutné vyčíslit hodnoty prvků. Lze tedy zpracovávat i nekonečné sekvence, ovšem za předpokladu, že se v řetězci zpracování, například na jeho konci, počet prvků nějakým způsobem omezí. Podívejme se na příklad:
fn main() { let iter1 = 1..; let iter2 = iter1.filter(|x| x % 2 == 0); let iter3 = iter2.take(10); // pruchod sekvenci se ziskanim indexu a hodnoty kazdeho prvku for (index,item) in iter3.enumerate() { println!("item[{}] = {}", index, item); } }
V tomto příkladu vytvoříme nekonečnou a současně i „línou“ sekvenci představovanou objektem typu range. Všechny (!) prvky této sekvence jsou následně profiltrovány a z výsledné nekonečné „líné“ sekvence je vybráno prvních deset prvků. Na první pohled by se mělo jednat o nekonečný program, protože anonymní funkci pro vyfiltrování sudých hodnot zdánlivě aplikujeme na nekonečný počet prvků, ovšem vzhledem k tomu, že je vyhodnocení „líné“, je ve skutečnosti zmíněná anonymní funkce zavolána jen dvacetkrát (zastavení zajišťuje funkce/metoda take):
item[0] = 2 item[1] = 4 item[2] = 6 item[3] = 8 item[4] = 10 item[5] = 12 item[6] = 14 item[7] = 16 item[8] = 18 item[9] = 20
Samozřejmě lze použít i metodu take_while:
fn main() { let iter1 = 1..; let iter2 = iter1.filter(|x| x % 2 == 0); let iter3 = iter2.take_while(|x| x*x < 256); // pruchod sekvenci se ziskanim indexu a hodnoty kazdeho prvku for (index,item) in iter3.enumerate() { println!("item[{}] = {}", index, item); } }
S následujícím výstupem:
item[0] = 2 item[1] = 4 item[2] = 6 item[3] = 8 item[4] = 10 item[5] = 12 item[6] = 14
Možné jsou i další kombinace, které ukazují, že i při zpracování nekonečných sekvencí nepotřebujeme ani nekonečnou paměť ani nekonečný čas:
let iter1 = 1..; let iter2 = iter1.filter(|x| x % 2 == 0); let iter3 = iter2.map(|x| x * x ); let iter4 = iter3.take(20); // pruchod sekvenci se ziskanim indexu a hodnoty kazdeho prvku for (index,item) in iter4.enumerate() { println!("item[{}] = {}", index, item); } }
Výsledkem je řada čtverců sudých hodnot:
item[0] = 4 item[1] = 16 item[2] = 36 item[3] = 64 item[4] = 100 item[5] = 144 item[6] = 196 item[7] = 256 item[8] = 324 item[9] = 400 item[10] = 484 item[11] = 576 item[12] = 676 item[13] = 784 item[14] = 900 item[15] = 1024 item[16] = 1156 item[17] = 1296 item[18] = 1444 item[19] = 1600
Ne všechny metody je však možné na nekonečné sekvence aplikovat. Například dříve popsaná metoda rev je aplikovatelná pouze na sekvence konečné, což je hlídáno při překladu:
fn main() { let iter1 = 1..; let iter2 = iter1.filter(|x| x % 2 == 0); let iter3 = iter2.rev(); let iter4 = iter3.take_while(|x| x*x < 256); // pruchod sekvenci se ziskanim indexu a hodnoty kazdeho prvku for (index,item) in iter3.enumerate() { println!("item[{}] = {}", index, item); } }
Překlad se v tomto případe nepodaří, a to z toho důvodu, že sekvencí není možné iterovat pozpátku:
error[E0277]: the trait bound `std::ops::RangeFrom<{integer}>: std::iter::DoubleEndedIterator` is not satisfied --> 46_iter16.rs:4:23 | 4 | let iter3 = iter2.rev(); | ^^^ | = note: required because of the requirements on the impl of `std::iter::DoubleEndedIterator` for `std::iter::Filter<std::ops::RangeFrom<{integer}>, [closure@46_iter16.rs:3:30: 3:44]>`
14. Funkce fold: postupné zpracování prvků sekvence
Poslední funkcí vyššího řádu, která nesmí chybět v repertoáru žádné funkcionálně zaměřené knihovny (a tím pádem ani v repertoáru metod iterátorů), je funkce pojmenovaná reduce popř. fold. Názvy těchto funkcí naznačují jejich účel – dochází k postupné redukci prvků uložených v sekvenci, a to (postupnou) aplikací zvolené uživatelské funkce na jednotlivé prvky a po krocích počítaný mezivýsledek. V programovacím jazyku Rust se příslušná metoda, která toto chování zajišťuje, jmenuje fold a akceptuje dva parametry – počáteční hodnotu „akumulátoru“ (mezivýsledku) a uzávěr, což je zjednodušeně řečeno funkce (typicky anonymní), která na sebe má navázánu alespoň jednu volnou proměnnou. Tento uzávěr je postupně volán se dvěma argumenty – aktuální hodnotou akumulátoru (mezivýsledku) a prvkem získaným ze vstupní sekvence. Akumulátor je po každém volání modifikován tak, že obsahuje hodnotou vrácenou uzávěrem.
Podívejme se na příklad, v němž sečteme všechny prvky zvolené sekvence. Tato sekvence obsahuje prvky s hodnotou 2, 4, 6 .. 20. Při postupně prováděném součtu je mezivýsledek nejprve nastaven na nulu, takže poprvé je anonymní funkce/uzávěr |sum, x| sum + x volána s parametry sum=0 a x=2, podruhé s parametry sum=2, x=4 atd.:
fn main() { let iter1 = 1..; let iter2 = iter1.filter(|x| x % 2 == 0); let iter3 = iter2.take(10); let suma = iter3.fold(0, |sum, x| sum + x); println!("sum = {}", suma); }
Po spuštění tohoto příkladu by se na standardním výstupu měla objevit jediná hodnota 110:
sum = 110
Nepatrně složitější příklad slouží pro výpočet tabulky faktoriálů. Zde si povšimněte, že počáteční hodnota mezivýsledku (akumulátoru) je nastavena na jedničku. Taktéž stojí za povšimnutí, že při konstrukci objektu typu range můžeme použít proměnné, nikoli nutně konstanty:
fn main() { for n in 1..10 { let fact = (1..n+1).fold(1, |prod, x| prod * x); println!("{}! = {}", n, fact); } }
Výsledkem tohoto programu bude tabulka s faktoriály prvních devíti přirozených čísel:
1! = 1 2! = 2 3! = 6 4! = 24 5! = 120 6! = 720 7! = 5040 8! = 40320 9! = 362880
15. Repositář s demonstračními příklady
Všechny dnes ukázané demonstrační příklady, resp. přesněji řečeno jejich bezchybné varianty, byly uloženy do Git repositáře dostupného na adrese https://github.com/tisnik/presentations. Příklady si můžete v případě potřeby stáhnout i jednotlivě bez nutnosti klonovat celý (dnes již objemný) repositář:
16. Odkazy na Internetu
- Rust Compare: Pointers & References
http://www.rust-compare.com/site/pointers.html - Rust Compare: Parameters
http://www.rust-compare.com/site/params.html - Why does this compile? Automatic dereferencing?
https://users.rust-lang.org/t/why-does-this-compile-automatic-dereferencing/2183 - Understanding Pointers, Ownership, and Lifetimes in Rust
http://koerbitz.me/posts/Understanding-Pointers-Ownership-and-Lifetimes-in-Rust.html - Rust lang series episode #25 — pointers (#rust-series)
https://steemit.com/rust-series/@jimmco/rust-lang-series-episode-25-pointers-rust-series - Rust – home page
https://www.rust-lang.org/en-US/ - Rust – Frequently Asked Questions
https://www.rust-lang.org/en-US/faq.html - The Rust Programming Language
https://doc.rust-lang.org/book/ - Rust (programming language)
https://en.wikipedia.org/wiki/Rust_%28programming_language%29 - Go – home page
https://golang.org/ - Stack Overflow – Most Loved, Dreaded, and Wanted language
https://stackoverflow.com/research/developer-survey-2016#technology-most-loved-dreaded-and-wanted - Rust vs Go (dva roky staré hodnocení, od té doby došlo k posunům v obou jazycích)
http://jaredforsyth.com/2014/03/22/rust-vs-go/ - Rust vs Go: My experience
https://www.reddit.com/r/golang/comments/21m6jq/rust_vs_go_my_experience/ - Friends of Rust (Organizations running Rust in production)
https://www.rust-lang.org/en-US/friends.html - Rust programs versus C++ g++
https://benchmarksgame.alioth.debian.org/u64q/compare.php?lang=rust&lang2=gpp - Další benchmarky (nejedná se o reálné příklady „ze života“)
https://github.com/kostya/benchmarks - Go na Redditu
https://www.reddit.com/r/golang/ - Rust vs. Go
http://vschart.com/compare/rust/vs/go-language - Abstraction without overhead: traits in Rust
https://blog.rust-lang.org/2015/05/11/traits.html - Functional Programming in Rust – Part 1 : Function Abstraction
http://blog.madhukaraphatak.com/functional-programming-in-rust-part-1/ - Of the emerging systems languages Rust, D, Go and Nim, which is the strongest language and why?
https://www.quora.com/Of-the-emerging-systems-languages-Rust-D-Go-and-Nim-which-is-the-strongest-language-and-why - Chytré ukazatele (moderní verze jazyka C++) [MSDN]
https://msdn.microsoft.com/cs-cz/library/hh279674.aspx - UTF-8 Everywhere
http://utf8everywhere.org/ - Rust by Example
http://rustbyexample.com/ - Rust oficiálně ve Fedoře
https://mojefedora.cz/rust-oficialne-ve-fedore/ - Resource acquisition is initialization
https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization - TIOBE index (October 2016)
http://www.tiobe.com/tiobe-index/ - Porovnání Go, D a Rustu na OpenHubu:
https://www.openhub.net/languages/compare?language_name[]=-1&language_name[]=-1&language_name[]=dmd&language_name[]=golang&language_name[]=rust&language_name[]=-1&measure=commits - String Types in Rust
http://www.suspectsemantics.com/blog/2016/03/27/string-types-in-rust/ - Trait (computer programming)
https://en.wikipedia.org/wiki/Trait_%28computer_programming%29 - Type inference
https://en.wikipedia.org/wiki/Type_inference