Відкрити головне меню

Clojure (вимовляється приблизно кложер, так само як англ. closure — Замикання) — сучасний діалект мови програмування Lisp. Це мова загального призначення, що підтримує інтерактивну розробку, зорієнтовану на функціональне програмування, спрощує багатотредове програмування, та містить риси сучасних скриптових мов.

Clojure
Clojure logo.gif
Парадигма:функціональна, мультипарадигматична
Дата появи:2007
Творці:Rich Hickey
Останній реліз:1.7 (30 липня, 2015; 4 роки тому (2015-07-30))
Система типізації:динамічна типізація, сувора типізація
Під впливом від:Common Lisp, Scheme, Prolog, ML, Haskell, Erlang[1]
Платформа:JVM, CLR
Ліцензія:Eclipse Public License
Сторінка інтернет:Офіційний вебсайт

Clojure працює на Java Virtual Machine і Common Language Runtime. Як і інші Lisp-подібні мови, Clojure розглядає код як дані і має потужну систему макросів.

Сирцевий код компілятора Clojure, бібліотек и runtime-компонентів розповсюджується в рамках ліцензії Eclipse Public License.

ФілософіяРедагувати

Rich Hickey розробив Clojure, оскільки йому був потрібен сучасний Lisp для функціонального програмування, розрахований на інтеграцію з розповсюдженою платформою Java й розроблений для паралельного програмування.[2][3]

Підхід Clojure до паралельності характеризується концепцією тотожностей,[4] що представляють серію незмінних станів протягом часу. Оскільки стани є незмінними значеннями, будь-яка кількість обробників може паралельно обробляти їх, і конкуренція зводиться до питання керування змінами від одного стану до іншого. З цією метою, Clojure надає декілька типів змінюваних посилань, кожен з яких має добре визначену семантику переходу між станами.

СинтаксисРедагувати

Як і інші Lisp-подібні мови, синтаксис Clojure побудовано на S-виразах (англ. S-expression), які в процесі синтаксичного розбору спершу перетворюються на структури даних за допомогою функції-читача (англ. reader), перш ніж компілюються. Clojure's reader підтримує літеральний синтаксис для хеш-таблиць, множин та векторів на додаток до списків, і вони передаються компілятору як є. Іншими словами, компілятор Clojure компілює не лише спискові структури даних, але й безпосередньо підтримує всі названі вище типи. Clojure — Lisp-1, і не є сумісним за кодом з іншими діалектами мови Lisp.

МакросиРедагувати

Система макросів Clojure дуже схожа на використовувану в Common Lisp, з тією відмінністю, що версія синтаксичного цитування Clojure (з використанням знаку `) доповнює символи їхніми просторами імен. Це допомагає запобігти ненавмисному перехопленню імен, оскільки прив'язка до імен, доповнених простором імен, заборонена. Є можливість форсувати таке захоплення імен, але це має бути зроблено явно. Clojure також не дозволяє переприв'язку глобальних імен з інших просторів імен, які були імпортовані в поточний простір.

Можливості мовиРедагувати

ВаріантиРедагувати

Реалізації Clojure для відмінних від JVM платформ:

ПрикладиРедагувати

Hello world!:

(println "Привіт, світе!")

Hello World з графічним інтерфейсом користувача:

(javax.swing.JOptionPane/showMessageDialog nil "Привіт, світе!" )

Thread-безпечний генератор унікальних серійних номерів:

(let [i (atom 0)]
  (defn generate-unique-id
    "Повертає унікальний числовий ID при кожному виклику."
    []
    (swap! i inc)))

Анонімний субклас java.io.Writer, що не пише нікуди, і макрос, що використовує це для відключення друку в своїх межах:

(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 "Привіт, ніхто!"))

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

Ханойська вежа. Функція, що генерує розв'язок задачі у вигляді лінивої послідовності, та форматований вивід результату:

(defn lazy-hanoi [n a b c]
  (lazy-seq 
    (if-not (= n 0)
        (lazy-cat (lazy-hanoi (- n 1) a c b) 
                  [[a b]] 
                  (lazy-hanoi (- n 1) c b a)))))
(defn print-hanoi [n a b c]
  (doseq [[from to] (lazy-hanoi n a b c)] 
         (printf "%s -> %s;\t" from to)))

Макрос, що здійснює обмін двох глобальних змінних. При кожному виклику макроса замість temp# підставляється новий унікальний ідентифікатор.

(defmacro swap [a b] 
   `(do (def temp# ~a) 
        (def ~a ~b) 
        (def ~b temp#)))

Відмінності від інших діалектів LispРедагувати

  • Clojure — мова, чутлива до регістру символів
  • Clojure — Lisp-1 (для змінних та функцій використовується спільний простір імен — так само, як у Scheme, але не в Common Lisp)
  • () — не те ж саме, що й nil
  • Рідер вільний від побічної дії.
  • Ключові слова не є символами.
  • Символи не є місцем зберігання даних (див. змінні).
  • nil — не символ.
  • t нема у синтаксисі, його функцію виконує true
  • Таблиця читання зараз недоступна для користувацьких програм.
  • let здійснює зв'язування послідовно.
  • do — не оператор циклу
  • Хвостова рекурсія не підтримується, для її заміни можна використати recur (якщо процедура викликає сама себе) чи trampoline (якщо декілька процедур викликають одна одну).
    • recur являє собою перехід на початок процедури чи циклу з присвоєнням нових значень параметрів. Обов'язково має бути у «хвостовій» позиції, що контролюється транслятором.
    • trampoline — функція, що забезпечує взаємний виклик функцій без переповнення стеку. Функції, що взаємодіють через trampoline, не викликають одна одну безпосередньо, а повертають функцію-замикання, що містить виклик. Якщо результатом функції є функція, trampoline викликає її, повторює цикл вже з її результатом і т. д., якщо ж результат має інший тип, trampoline завершує роботу і повертає його.
  • Синтаксичне цитування (syntax-quote, оператор `) здійснює розв'язку символів, доповнюючи їх просторами імен, тому `x — не те ж саме, що 'x.
  • Синтаксичні цитати дозволяють автоматично генерувати символи.
  • ~ (тильда) — розцитовування (unquote), тоді як ',' (кома) вважається пробілом.
  • Існує синтаксис на рівні рідера для асоціативних таблиць (maps), множин та векторів.
  • cons, first та rest маніпулюють з абстракцією послідовності, необов'язково з елементами cons.
  • Більшість структур даних — незмінювані.
  • Замість lambda використовується fn, що може мати декілька методів.
  • = — предикат рівності (equality), що може порівнювати не лише числа, а й інші об'єкти.
  • Всі (глобальні) змінні можна динамічно переприв'язувати без конфлікту з лексичними локальними прив'язками. Спеціальні оголошення для розрізнення динамічних та лексичних прив'язок — не потрібні . Оскільки Clojure — Lisp-1, (глобальні) функції можуть динамічно переприв'язуватись (перевизначатись).
  • Нема letrec, labels чи flet — для посилання на себе використовується (fn ім'я [аргументи]…), для взаємних посилань — letfn.
  • У Clojure nil позначає відсутність значення будь-якого типу і не прив'язаний до списків чи послідовностей.
  • Порожні колекції не є еквівалентом nil. Clojure не прирівнює nil до '().
  • false — одне з двох можливих булевих значень, інше — true
  • Серед колекцій є не лише списки. Можуть існувати порожні колекції, деякі з них підтримуються літералами ([], {} та ()). Тому не може бути якогось сторожового значення порожньої колекції.
  • Порівнюючи зі Scheme, nil можна розглядати як аналог #f.
  • Найбільша відмінність Clojure — послідовності. Це не якийсь окремий тип колекцій, особливо враховуючи, що їм необов'язково бути саме списками. При спробі отримати з порожньої колекції послідовність її елементів (викликом seq) повертається nil. При спробі отримати з послідовності (на її останньому елементі) залишок (rest) буде повернуто іншу логічну послідовність. Перевірити, чи ця послідовність порожня, можна шляхом виклику seq. Це дозволяє послідовностям та протоколу послідовностей бути лінивими.
  • Деякі функції для роботи з послідовностями відповідають функціям зі Scheme та CL, що маніпулюють лише з парами/cons ('списками') і повертають сторожове значення ('() або nil), яке представляє 'порожній' список. Їх результат у Clojure відрізняється тим, що повертається не специфічна порожня колекція, а інша логічна послідовність. Частина функцій для роботи з послідовностями не мають відповідників у Scheme/CL і являють собою Haskell/ML-подібні функції. Деякі з них повертають нескінченні або обчислювані послідовності, де аналогія з конкретними структурами даних, такими як списки у Scheme/CL, лише приблизна.
  • Це допомагає відокремити колекції/структури даних від послідовностей/ітерацій. Як у CL, так і у Scheme вони об'єднані, у Clojure — відокремлені.

ПриміткиРедагувати

  1. Rich Hickey (2009-06-30). Books that influenced Clojure. Архів оригіналу за 2012-04-18. Процитовано 2009-09-11. 
  2. Rationale. Rich Hickey. clojure.org. Архів оригіналу за 2012-04-18. Процитовано 2008-10-17. 
  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. Архів оригіналу за 2013-07-12. Процитовано 2010-03-01. 
  5. clojure/clojure-clr · GitHub. Github.com. Процитовано 2012-06-28. 
  6. clojure/clojurescript · GitHub. Github.com. Процитовано 2012-06-28. 
  7. aemoncannon (2010-12-30). Home · aemoncannon/las3r Wiki · GitHub. Github.com. Процитовано 2012-06-28. 
  8. halgari/clojure-py · GitHub. Github.com. Архів оригіналу за 2016-01-23. Процитовано 2012-07-10. 
  9. rouge-lang/rouge · GitHub. Github.com. Процитовано 2013-01-25. [недоступне посилання з лютий 2019]

ЛітератураРедагувати

ПосиланняРедагувати