Clojure

γλώσσα προγραμματισμού

Η Clojure (προφέρεται όπως ο αγγλικός όρος "closure"[1]) είναι μια σύγχρονη διάλεκτος της γλώσσας προγραμματισμού Lisp. Είναι γλώσσα γενικού σκοπού, υποστηρίζει τη διαδραστική ανάπτυξη, ενθαρρύνει το στυλ συναρτησιακού προγραμματισμού και απλοποιεί τον πολυνηματικό προγραμματισμό. Η Clojure τρέχει στην Εικονική μηχανή της Java (JVM) και στην Common Language Runtime (CLR). Η Clojure τηρεί τη φιλοσοφία "κώδικας-σαν-δεδομένα (code-as-data)" και έχει ένα εκτεταμένο σύστημα μακροεντολών.

Φιλοσοφία

Επεξεργασία

Ο Rich Hickey ανέπτυξε την Clojure γιατί ήθελε μια σύγχρονη διάλεκτο της Lisp για συναρτησιακό προγραμματισμό, που να συνεργάζεται με την καθιερωμένη πλατφόρμα της Java και να είναι σχεδιασμένη για ταυτοχρονισμό.[2][3]

Η προσέγγιση της Clojure στον ταυτοχρονισμό χαρακτηρίζεται από την έννοια των identities[4], τα οποία αναπαριστούν αμετάβλητες καταστάσεις στο χρόνο. Επειδή η κατάσταση αποτελείται από αμετάβλητες τιμές, οποιοσδήποτε αριθμός υποπρογραμμάτων-"εργατών" ("workers") μπορεί να τις χρησιμοποιεί παράλληλα, και ο ταυτοχρονισμός εξαρτάται από το πώς ελέγχονται οι αλλαγές από τη μια κατάσταση στην άλλη. Για αυτόν το λόγο, η Clojure παρέχει αρκετούς τύπους μεταβλητής αναφοράς (mutable reference types), ο καθένας από τους οποίους έχει ξεκάθαρη σημασιολογία όσον αφορά τη μετάβαση μεταξύ των καταστάσεων.

Όπως και όλες οι άλλες Lisp, η σύνταξη της Clojure βασίζεται σε συμβολικές εκφράσεις (S-expressions) που αρχικά διαβάζονται σε ειδικές δομές δεδομένων από έναν αναγνώστη (reader) πριν τη μεταγλώττιση. Εκτός από λίστες, ο αναγνώστης της Clojure υποστηρίζει ρητή σύνταξη για αντιστοιχίσεις, σύνολα και διανύσματα (vectors), και αυτά δίνονται στο μεταγλωττιστή όπως είναι. Με άλλα λόγια, ο μεταγλωττιστής της Clojure δε μεταγλωττίζει μόνο λίστες αλλά υποστηρίζει άμεσα όλους τους προαναφερθέντες τύπους. Η Clojure είναι μια Lisp-1, και δεν προορίζεται να είναι συμβατή σε επίπεδο κώδικα με άλλες διαλέκτους της Lisp.

Μακροεντολές

Επεξεργασία

Το σύστημα μακροεντολών της Clojure μοιάζει με αυτό της Common Lisp με την εξαίρεση ότι η έκδοση της Clojure για τον τελεστή backquote (που καλείται "syntax quote") δίνει στα σύμβολα και το χώρο ονομάτων τους (namespace). Επειδή απαγορεύεται η δέσμευση ονομάτων που περιέχουν χώρο ονομάτων, αποφεύγονται καταστάσεις ακούσιας δέσμευσης ονομάτων. Μια επέκταση μακροεντολής μπορεί να δεσμεύει ονόματα, αλλά αυτό πρέπει να γίνει ρητά από τον προγραμματιστή.

Η Clojure επίσης απαγορεύει την επαναδέσμευση καθολικών ονομάτων σε άλλους χώρους ονομάτων που έχουν εισαχθεί (import) στον τρέχοντα χώρο ονομάτων.

Χαρακτηριστικά της γλώσσας

Επεξεργασία
  • Δυναμική ανάπτυξη με βρόχο read-eval-print
  • Οι συναρτήσεις είναι αντικείμενα πρώτης τάξης με έμφαση στην αναδρομή αντί για την επανάληψη με παρενέργειες
  • Οκνηρές ακολουθίες (lazy sequences)
  • Πλούσιο σύνολο από αμετάβλητες διαρκείς δομές δεδομένων (immutable persistent data structures)
  • Ταυτοχρονισμός μέσω μνήμης συναλλαγών σε λογισμικό (software transactional memory), συστήματος με agents, και συστήματος δυναμικών μεταβλητών
  • Οι πολυμέθοδοι (multimethods) επιτρέπουν δυναμική αποστολή (dynamic dispatch) στους τύπους και τις τιμές οποιουδήποτε συνόλου από ορίσματα (πρβ. ο κλασικός αντικειμενοστρεφής πολυμορφισμός χρησιμοποιεί τον τύπο αυτού που είναι πρακτικά το πρώτο όρισμα μιας μεθόδου)
  • Η Clojure είναι μεταγλωττιζόμενη γλώσσα που παράγει κώδικα byte για την JVM
  • Πλήρης ενσωμάτωση με τη Java: Μεταγλωττιζόμενες σε κώδικα για την JVM, οι εφαρμογές σε Clojure μπορούν εύκολα να πακεταριστούν, να εγκατασταθούν και να εκτελεστούν σε υλοποιήσεις JVM και σε εξυπηρετητές εφαρμογών χωρίς πολυπλοκότητα. Η γλώσσα παρέχει επίσης μακροεντολές που απλοποιούν τη χρήση των υπαρχόντων Java API. Όλες οι δομές δεδομένων της Clojure υλοποιούν βασικα interfaces της Java, διευκολύνοντας την εκτέλεση κώδικα της Clojure από τη Java.

Παραδείγματα

Επεξεργασία

"Γεια σου, κόσμε!":

(println "Γεια σου, κόσμε!")

"Γεια σου, κόσμε!" (GUI):

(javax.swing.JOptionPane/showMessageDialog nil "Γεια σου, κόσμε!")

Μια γεννήτρια μοναδικών σειριακών αριθμών ασφαλής για πολυνηματική εκτέλεση (thread-safe):

(let [i (atom 0)]
  (defn generate-unique-id
    "Επιστρέφει ένα μοναδικό αριθμητικό ID για κάθε κλήση."
    []
    (swap! i inc)))

Μια ανώνυμη υποκλάση της java.io.Writer που δεν γράφει πουθενά, και μια μακροεντολή που τη χρησιμοποιεί για να κρύψει όλες τις εντολές εξόδου (print) σε αυτή:

(def bit-bucket-writer
  (proxy [java.io.Writer] []
    (write [buf] nil)
    (close []    nil)
    (flush []    nil)))

(defmacro noprint
  "Αποτιμά όλες τις εκφράσεις που δίνονται, αποσιωπώντας την έξοδο *out*."
  [& forms]
  `(binding [*out* bit-bucket-writer]
     ~@forms))

(noprint
 (println "Hello, nobody!"))

10 νήματα που χειρίζονται μια κοινή δομή δεδομένων, η οποία αποτελείται από 100 διανύσματα, το καθένα από τα οποία περιέχει 10 (αρχικά διαδοχικούς) αριθμούς. Κάθε νήμα τότε επαναλαμβανόμενα επιλέγει δύο τυχαίες θέσεις σε δύο τυχαία διανύσματα και τα αντιμεταθέτει:

Όλες οι αλλαγές στα διανύσματα συμβαίνουν μέσα σε συναλλαγές χρησιμοποιώντας το σύστημα μνήμης συναλλαγών σε λογισμικό της Clojure. Με αυτόν τον τρόπο, ακόμα και μετά από 100.000 επαναλήψεις κάθε νήματος δε χάνεται κανένας αριθμός.

(defn run [nvecs nitems nthreads niters]
  (let [vec-refs (vec (map (comp ref vec)
                           (partition nitems (range (* nvecs nitems)))))
        swap #(let [v1 (rand-int nvecs)
                    v2 (rand-int nvecs)
                    i1 (rand-int nitems)
                    i2 (rand-int nitems)]
                (dosync
                 (let [temp (nth @(vec-refs v1) i1)]
                   (alter (vec-refs v1) assoc i1 (nth @(vec-refs v2) i2))
                   (alter (vec-refs v2) assoc i2 temp))))
        report #(do
                 (prn (map deref vec-refs))
                 (println "Distinct:"
                          (count (distinct (apply concat (map deref vec-refs))))))]
    (report)
    (dorun (apply pcalls (repeat nthreads #(dotimes [_ niters] (swap)))))
    (report)))

(run 100 10 10 100000)

Έξοδος του προηγούμενου παραδείγματος:

([0 1 2 3 4 5 6 7 8 9] [10 11 12 13 14 15 16 17 18 19] ...
 [990 991 992 993 994 995 996 997 998 999])
Distinct: 1000
 
([382 318 466 963 619 22 21 273 45 596] [808 639 804 471 394 904 952 75 289 778] ...
 [484 216 622 139 651 592 379 228 242 355])
Distinct: 1000

Δείτε επίσης

Επεξεργασία

Παραπομπές

Επεξεργασία
  1. http://groups.google.com/group/clojure/msg/766b75baa7987850?
  2. «Rationale». Rich Hickey. clojure.org. Ανακτήθηκε στις 17 Οκτωβρίου 2008. 
  3. http://channel9.msdn.com/shows/Going+Deep/Expert-to-Expert-Rich-Hickey-and-Brian-Beckman-Inside-Clojure/
  4. «On State and Identity». Rich Hickey. clojure.org. Ανακτήθηκε στις 1 Μαρτίου 2010. 

Βιβλιογραφία

Επεξεργασία

Εξωτερικοί σύνδεσμοι

Επεξεργασία