Zobrazují se příspěvky se štítkemstm. Zobrazit všechny příspěvky
Zobrazují se příspěvky se štítkemstm. Zobrazit všechny příspěvky

29. července 2011

Jak měnit neměnitelné. Refs

Clojure používá neměnitelná (immutable) data/objekty. Pokud potřebujeme měnitelná (mutable) data, řeší to Clojure "měnitelnou referencí na neměnitelný objekt" :-) Jedním z prostředků, které toto řeší jsou Refs: jsou to transakční reference, které umožňují bezpečné sdílení měnitelných úložišť pomocí systému STM (Software Transactional Memory).

Měnitelná reference (ref) se vytvoří funkcí ref, její hodnotu vrací buď funkce deref, nebo makro @:
(def my-ref (ref "immutable data"))
; #'user/my-ref
my-ref
; #<ref@18352d8: "immutable data">
@my-ref
; "immutable data"
(deref my-ref)
; "immutable data"
Pokud chceme referenci nastavit na jinou (neměnitelnou) hodnotu, slouží k tomu funkce ref-set:
(ref-set my-ref "another immutable data")
; IllegalStateException No transaction running  clojure.lang.LockingTransaction.getEx (LockingTransaction.java:208)
Jejda! Zapomněli jsme na transakci :-)
(dosync (ref-set my-ref "another immutable data"))
; "another immutable data"
@my-ref
; "another immutable data"
Pokud chceme provést čtení hodnoty a zároveň její změnu v jednom kroku (= aplikovat na hodnotu funkci), je vhodné použít funkci alter:
(dosync (alter my-ref #(apply str (reverse %))))
; "atad elbatummi rehtona"
@my-ref
; "atad elbatummi rehtona"
Na reference je také možné přidat validace:
(def counter (ref 0 :validator number?))
; #'user/counter
(dosync (ref-set counter "string"))
; IllegalStateException Invalid reference state  clojure.lang.ARef.validate (ARef.java:33)
@counter
; 0
(dosync (ref-set counter 42))
; 42
@counter
; 42
O Refs jsem už jednou (trochu) psal. Z dnešního pohledu k tomu mám dvě výhrady:
  • místo ref-set jsem měl použít alter,
  • místo reference jsem měl použít atom.
ref-set je vhodné použít tehdy, pokud přiřazuji novou hodnotu (nepočítám ji). alter tehdy, pokud nad hodnotou provádím nějakou funkci (např. inkrementace, přidání hodnoty do kolekce apod.).

Při rozhodování, jestli použít ref nebo atom je podstatné, jestli využiju transakci - v transakci můžu updatovat více referencí. Pokud budu měnit pouze jedinou hodnotu (bez vazby na cokoli jiného) je vhodnější použít atom (a o těch až někdy příště).

27. dubna 2011

Second mission

Navázal bych na post First Mission - napsal jsem další transformační skriptík pro stejný projekt. Opět šlo o to, přečíst vstupní soubor a na základě daných podmínek změnit některé hodnoty. Soubor obsahoval informaci o sdílených telefonech:

Tam, kde byla pro stejný telefon různá osobní čísla mělo dojít ke změně typu z PERSONAL na SHARED:
Měl jsem trochu obavy z "performance" (soubory mají 10k - 50k řádků), tak jsem to udělal na dva průchody - při prvním průchodu se do mapy vložily telefony, které měly více os. čísel a při druhém průchodu, kdy se všechny záznamy zapisovaly do upraveného souboru, se tyto záznamy měnily.

Skriptík je samozřejmě triviální, ale podstatné jsou věci, které jsem se přitom naučil - pořádně pracovat s Refs a transakcema (STM):
(def phones (ref {}))

(defn add-phone [phone pers-num]
  (if (contains? @phones phone)
      (if (contains? (@phones phone) pers-num)
          (println "Duplicate record:" phone pers-num)
          (dosync
            (ref-set phones (update-in @phones [phone]
                                       conj pers-num))))
      (dosync
        (ref-set phones (assoc @phones phone
                               #{pers-num})))))

(add-phone :123456789 :12)
(add-phone :123456789 :42)
(add-phone :123456789 :12)
(add-phone :123456790 :36)

(println @phones)
; Duplicate record: :123456789 :12
; {:123456790 #{:36}, :123456789 #{:12 :42}}
Další šikovná věc je "filrování mapy":
(defn get-shared [m]
  (select-keys m (for [[k v] m :when
                       (> (count v) 1)] k)))

(println
  (get-shared
    {:123456790 #{:36}, :123456789 #{:12 :42}})
; {:123456789 #{:12 :42}}
K tématu Refs bych se v budoucnu ještě rád vrátil - hned potom, co ze zásobníku vyndám a zpracuju sekvence.