To je zajimava otazka a dekuji za ni. Plati, ze prvky lazy sekvence zustanou v pameti tak dlouho, dokud na tuto sekvenci existuje reference, tj. (kdyz to prevedu do Javovske reci) existuje promenna ci parametr, ktery referencuje objekty typu sekvence. Funguje to tak, ze napriklad pri druhem volani funkce "nth" nebo "take" se uz prvky vyhodnotit nemusi, ovsem predpoklada se, ze se pouzivaji ciste funkce bez vedlejsiho efektu, coz rand neni :-) [nekde je ulozen predchozi stav generatoru pseudonahodnych cisel, ktery se volanim (rand) zmeni].
Asi uvedu i priklad, doufam, ze projde formatovani. Mejme jednoduchou funkci:
(defn increment [x] (do (println "computing " x) (inc x)))
u ni je zajimave, ze jako vedlejsi efekt pri svem zavolani "neco" vypise na stdout.
Ted vytvorime lazy sekvenci:
(def mysequence (map increment (range 1 10)))
... a nic se nevypsalo! protoze Clojure je liny lazy sekvenci vyhodnocovat - ostatne ani nemusi, alespon prozatim :)
Ziskame prvni 4 prvky:
(take 4 mysequence) (computing 1 computing 2 computing 3 computing 4 computing 5 computing 6 computing 7 computing 8 computing 9 2 3 4 5)
ted doslo k vyhodnoceni sekvence, coz prozradi printy - vedlejsi efekt
A co kdyz "take" zavolame znovu?:
(take 4 mysequence) (2 3 4 5)
nic se krome navratove hodnoty take nevypsalo = uz se nase funkce nevola protoze mame vysledky "cachovany"
To sice vypadá zajímavě, ale nelíbí se mi, že se to dělá automaticky a tímpádem programátor musí myslet na to, jestli generátor obsahuje nějaké vedlejší efekty nebo vstupy, nebo ne.
Co kdybych chtěl např. lazy sekvenci, která by mi vracela *aktuální* obsah souboru? Dalo by se to nějak udělat ("vypnout" cachování)?
jeste pridam zajimavou informaci, co jsem pred chvili dohledal: "Rich Hickey, the designer of Clojure, responded by saying that he had already experimented with uncached lazy sequences, and that such a choice causes a different set of problems with performance problems and code breaking – problems which he found to be even more common than the ones I've raised here."
Ten post vypadá zajímavě, teď nemám čas/chuť to číst, ale určitě se k tomu vrátím, včetně toho vlákna v konfeře Clojure, kde tohle Hickey říká. Dost by mě totiž zajímalo, čím tohle tvrzení podkládá.
Autor postu má argumenty (imho) silný a logický, přesně tohle mě právě napadlo taky:
First, it's easier to convert an uncached sequence to a cached sequence than vice versa. Second, if you forget to cache something that should be cached, it's merely a performance optimization problem, but if you forget to uncache something that needs to be cached, your program can crash. So, it's safer if the language defaults to uncached.
Přijde mi, že by bylo lepší použít uncached sekvence a k tomu cacheovací wrapper, který by programátor mohl a nemusel použít, podle vlastního uvážení.
Chtel bych podekovat za krasny serial. Closure se mi nesmirne libi.
Jenom bych se chtel ujistit, ze chapu dobre pouziti fce apply. Pokud apply predam pouze dva parametry (tzn. fce a "ten posledni"), bude zavolana fce s "rozbalenym" poslednim parametrem (predpokladam, ze se rozbaluje pouze jedna uroven). Tzn.
(apply fce [d [e] f])
je zrejme chybne v pripade, ze fce neumi pracovat s vektory. Toto mi tedy umoznuje na misto posledniho parametru predat lazy sekvenci (tzn. v dobe kompilace neznamy pocet prvku). Pokud vsak znam nektere parametry jiz v dobe kompilace, mohu je zapsat jeste pred tim poslednim parametrem. Tyto se vsak jiz nebudou "rozbalovat". Je to tak?
(apply fce nerozbali take_nerozbali [tohle rozbali])
Ano, je to tak, pro apply je dulezity pouze jeji prvni parametr (vlastni funkce) a posledni parametr (obecne lazy sekvence a samozrejme vsechno specialnejsiho, napriklad vektor, seznam...). Ty parametry "mezi" jsou tam proto, ze nektere funkce proste vyzaduji vetsi pocet parametru, nejenom jednu rozbalenou sekvenci. Nejjednodussi priklad je prave scitani, tam se ale moc nepozna, jak to vlastne funguje - co se quotuje a co rozbaluje:
user=> (apply + [1 2 3]) 6 user=> (apply + '(1 2 3)) 6 user=> (apply + 1 2 3 '(1 2 3)) 12
To je ovsem jednoduche, zajimavejsi je napriklad toto pouziti:
(map (fn [x] (apply max x)) '( [1 2 3] [4 5 6] [7 8 9])) (3 6 9)
Nie je nic lahsie ako si to overit :)
Staci ist na http://tryclj.com a zadat napr.:
(apply println [1 [2] 3])
(apply println [1] [2] [3])
Pravda :) Mne teda v Opere ten webovy interpret nechodi (mam zakazane predavani klavesoych eventu pro javascript) a po ruce jsem zrovna clojure nemel. Ale nyni mam a je to skutecne tak, jak jsem to pochopil :).
Mimochodem println se chova take zajimave - nedoporucuji ji pred prectenim dokumentace/ozkousenim pouzivat.
I bez zakazu JS (nebo jeho casti) to v Opere chodi nejak divne - kdyz je text vetsi nez okno te konzole, tak se pri zapisu porad skace na zacatek a hned zase na konec (jakoby to napred fokusovalo prvni radek a hned potom radek s kurzorem).
Na druhou stranu ma clojure-1.4.0-slim.jar velikost nejakych 850 kB, coz je dneska zanedbatelne (kdysi zhruba polovina kapacity diskety :))), tak to taham s sebou na USB disku - co kdyby