Nekonečná lenost sekvencí

Velmi silnou (a zajímavou) zbraní Clojure jsou sekvence (neboli seq, čti [si:k]). Sekvence je logický seznam, který implementuje jednoduché rozhraní ISeq a umožňuje sekvenční přístup k datům — a to nejenom k těm, u kterých bychom to čekali (kolekce = seznamy, mapy, apod.), ale i k těm, kde potřebné sekvenční implementační detaily chybí, např. stromové struktury (XML, adresáře), databázové result sety či textové soubory (buď jeden velký řetězec, nebo vektor řádků), I/O streamy, anebo obyčejné znakové řetězce (String).

Věc, na kterou bych se chtěl podívat je jednak lenost (laziness) a jednak (možná) nekonečnost (infiniteness) sekvencí. Laziness je známá např. z databázového/ORM světa, kdy se lazy typicky dotahují data v 1:N vztazích. Výhodou lazy sekvencí je:

  • odsunutí výpočtu, který možná nebude potřeba,
  • možnost práce s velkými daty, která se nevejdou do paměti,
  • odsunutí I/O operací, až budou opravdu potřeba.

Co se týká nekonečnosti, tam je to jasné — některé sekvence prostě jsou nekonečné: přirozená čísla, prvočísla, Fibonacciho posloupnost, atd.

Ukázkový příklad jsem převzal z knížky Programming Clojure a sice protože mi přišel tak dobrý, že se mi zdálo zbytečné vymýšlet příklad vlastní (ach ta lenost :-). Zajímá vás, jak vypadá milionté prvočíslo?

(use '[clojure.contrib.lazy-seqs :only (primes)])
; nil
(def ordinals-and-primes
  (map vector (iterate inc 1) primes))
; #'user/ordinals-and-primes
(first (drop 999999 ordinals-and-primes))
; [1000000 15485863]

Pro vysvětlení, Var primes obsahuje lazy sekvenci prvočísel, Var ordinals-and-primes obsahuje dvojice hodnot [pořadové-číslo prvočíslo]. Poslední příkaz (first (drop ...)) provede samotný výpočet sekvence prvočísel, zahodí prvních 999.999 hodnot a vrátí (pomocí first) tu 1.000.000tou. Vypočítat milion prvočísel chvilku trvá, takže třetí příkaz chvilku poběží. Spočítanou sekvenci už má pak ale Clojure nacachovanou, takže hodnoty pod milion nebo lehce nad vrací okamžitě.