A Clojure programozási nyelv

Programhelyesség

A nyelv biztosít eszközöket a programok helyességének futásidejű ellenőrzésére is az elő- és utófeltételek nyelvi, illetve runtime szinten történő bevezetésével.

Elő és utófeltételek

A függvényekhez kapcsolt metaadatok révén lehetőség nyílik a nyelvben elő- és utófeltételek futásidejű ellenőriztetésére is:

    user=> (defn negyzet [x]
              {:pre [(> x 10)]
               :post [(< % 2200)]}
             (* x x))
    #'user/negyzet

    user=> (negyzet 1000)
    java.lang.AssertionError: Assert failed: (< % 2200) (NO_SOURCE_FILE:0)
    
    user=> (negyzet 2)
    java.lang.AssertionError: Assert failed: (> x 10) (NO_SOURCE_FILE:0)

Az elő- utófeltételeket paraméterként is beinjektálhatjuk a függvénybe:

    user=> (defn check
             ([pre f post] {:pre [pre] :post [post]} f)
             ([pre f x post] {:pre [pre] :post [post]} (f x)))

    user=> (check (> x 10) negyzet #(< % 2200))    ; ha nem adunk át neki paramétert, egy ellenőrzéssel ellátott f-fel tér vissza
    #<user$negyzet user$negyzet@107d6ae>

    user=> ( (check (> x 10) negyzet #(< % 2200)) 1000)       ; amit meghívhatunk....
    java.lang.AssertionError: Assert failed: (< % 2200) (NO_SOURCE_FILE:0)

    user=> (check (> x 10) negyzet 12000 #(< % 2200))   ; ha paramétert is adunk a check-nek, végrehajtja a függvényt, elő/utófeltétel ellenőrzéssel
    java.lang.AssertionError: Assert failed: (< % 2200) (NO_SOURCE_FILE:0)

Invariánsok elő és utófeltételekkel

Makrók segítségével különböző ciklus-"invariánsok" futásidejű ellenőrzését végző szerkezeteket is írhatunk. Írjunk egy olyan ciklust, ami minden iteráció elején ellenőriz egy, a ciklusban megadott logikai állítást!

    (defn index-filter
      "Megszuri a sorozatot az elem indexenek predikatuma alapjan"
      [pred coll]
      (for [i (range (count coll)), :when (pred i)] (get coll i)))

    (defn odd-indexes
      "Visszaadja egy lista paratlan elemeit: elsot, harmadikat, otodiket, stb."
      [coll]
      (index-filter (comp odd? inc) coll))

    (defn even-indexes
      "Visszaadja egy lista paros elemeit: masodikat, negyediket, hatodikat, stb."
      [coll]
      (index-filter (comp even? inc) coll))

    (defmacro loopin [bindings _ inv body]
      `(letfn [(loop_fn# ~(vec (odd-indexes bindings))  
                         {:pre ~inv}
                         ~body)]
               (loop_fn# ~@(even-indexes bindings))))

A loopin (invariánssal kibővített loop) makró használatát a következő példán is láthatjuk:

    (defn lnko[x y]
       "Visszaadja a ket parameter legnagyobb kozos osztojat"
       (loopin [a x b y] :invariant [(number? a) (number? b)]
          (if (= b 0)
              a
             (recur b (mod a b)))))

Látható, hogy az lnko minden iterációjában ellenőrizzük, hogy a két paraméter típusa valóban szám-e, és ha az egyik állítás nem teljesül, hibát kapunk:

    user=> (lnko 24 36)
    12
    
    user=> (lnko 24 "36")
    java.lang.AssertionError: Assert failed: (number? b) (NO_SOURCE_FILE:0)