Clojure
Clojure (вимовляється приблизно кложер, так само як англ. closure — Замикання) — сучасний діалект мови програмування Lisp. Це мова загального призначення, що підтримує інтерактивну розробку, зорієнтовану на функціональне програмування, спрощує багатопотокове програмування, та містить риси сучасних скриптових мов.
Clojure | |
---|---|
Парадигма | функціональна, мультипарадигматична |
Дата появи | 2007 |
Творці | Rich Hickey |
Розробник | Річард Хіккі |
Останній реліз | 1.11.1 (5 квітня 2022 ) |
Система типізації | динамічна типізація, сувора типізація |
Під впливом від | Common Lisp, Scheme, Prolog, ML, Haskell, Erlang[1] |
Мова реалізації | Java[2] |
Платформа | JVM, CLR |
Ліцензія | Eclipse Public License |
Звичайні розширення файлів | .clj , .cljs , .cljc , .edn або .cljr |
Репозиторій вихідного коду | github.com/clojure/clojure |
Вебсайт | Офіційний сайт |
Clojure працює на Java Virtual Machine і Common Language Runtime. Як і інші Lisp-подібні мови, Clojure розглядає код як дані і має потужну систему макросів.
Вихідний код компілятора Clojure, бібліотек і runtime-компонентів розповсюджується в рамках ліцензії Eclipse Public License.
Філософія
ред.Річард Гіккі розробив Clojure, оскільки йому був потрібен сучасний Lisp для функціонального програмування, розрахований на інтеграцію з розповсюдженою платформою Java й розроблений для паралельного програмування.[3][4]
Підхід Clojure до паралельності характеризується концепцією тотожностей,[5] що представляють серію незмінних станів протягом часу. Оскільки стани є незмінними значеннями, будь-яка кількість обробників може паралельно обробляти їх, і конкуренція зводиться до питання керування змінами від одного стану до іншого. З цією метою, Clojure надає декілька типів змінюваних посилань, кожен з яких має добре визначену семантику переходу між станами.
Синтаксис
ред.Як і інші Lisp-подібні мови, синтаксис Clojure побудовано на S-виразах (англ. S-expression), які в процесі синтаксичного розбору спершу перетворюються на структури даних за допомогою функції-читача (англ. reader), перш ніж компілюються. Clojure's reader підтримує літеральний синтаксис для хеш-таблиць, множин та векторів на додаток до списків, і вони передаються компілятору як є. Іншими словами, компілятор Clojure компілює не лише спискові структури даних, але й безпосередньо підтримує всі названі вище типи. Clojure — Lisp-1, і не є сумісним за кодом з іншими діалектами мови Lisp.
Макроси
ред.Система макросів Clojure дуже схожа на використовувану в Common Lisp, з тією відмінністю, що версія синтаксичного цитування Clojure (з використанням знаку `) доповнює символи їхніми просторами імен. Це допомагає запобігти ненавмисному перехопленню імен, оскільки прив'язка до імен, доповнених простором імен, заборонена. Є можливість форсувати таке захоплення імен, але це має бути зроблено явно. Clojure також не дозволяє переприв'язку глобальних імен з інших просторів імен, які були імпортовані в поточний простір.
Можливості мови
ред.- Компільована мова, що генерує байткод для JVM
- Тісна інтеграція з Java: відкомпільовані в байткод JVM, програми на Clojure можуть пакуватися та запускатися на JVM-серверах без додаткових ускладнень. Мова також надає макроси, які полегшують використання наявного Java API. Всі структури даних Clojure реалізують стандартні інтерфейси Java, що робить простим запуск Java коду, розробленого на Clojure.
- Динамічна розробка з використанням REPL
- Функції як об'єкти першого класу
- Наголос на рекурсії замість циклів з побічним ефектом
- Ліниві послідовності
- Надає широкий набір незмінюваних персистентних структур даних
- Паралельне програмування з використанням програмної транзакційної пам'яті, система агентів, система динамічних змінних
- Мультиметоди (аналог перевантаження (англ. overloading) функцій), що підтримують динамічний вибір методу за типами та значеннями довільного набору аргументів (пор. звичайний об'єктноорієнтований поліморфізм, де вибір здійснюється за типом (фактично) першого аргументу)
Варіанти
ред.Реалізації Clojure для відмінних від JVM платформ:
Приклади
ред.(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
(when-not (zero? n)
(lazy-cat (lazy-hanoi (dec n) a c b)
[[a b]]
(lazy-hanoi (dec n) 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 — відокремлені.
Примітки
ред.- ↑ Rich Hickey (30 червня 2009). Books that influenced Clojure. Архів оригіналу за 18 квітня 2012. Процитовано 11 вересня 2009.
- ↑ The clojure Open Source Project on Open Hub: Languages Page — 2006.
- ↑ Rationale. Rich Hickey. clojure.org. Архів оригіналу за 18 квітня 2012. Процитовано 17 жовтня 2008.
- ↑ Архівована копія. Архів оригіналу за 9 листопада 2017. Процитовано 1 березня 2011.
{{cite web}}
: Обслуговування CS1: Сторінки з текстом «archived copy» як значення параметру title (посилання) - ↑ On State and Identity. Rich Hickey. clojure.org. Архів оригіналу за 12 липня 2013. Процитовано 1 березня 2010.
- ↑ clojure/clojure-clr · GitHub. Github.com. Архів оригіналу за 25 серпня 2013. Процитовано 28 червня 2012.
- ↑ clojure/clojurescript · GitHub. Github.com. Архів оригіналу за 18 травня 2014. Процитовано 28 червня 2012.
- ↑ aemoncannon (30 грудня 2010). Home · aemoncannon/las3r Wiki · GitHub. Github.com. Архів оригіналу за 2 січня 2015. Процитовано 28 червня 2012.
- ↑ halgari/clojure-py · GitHub. Github.com. Архів оригіналу за 23 січня 2016. Процитовано 10 липня 2012.
- ↑ rouge-lang/rouge · GitHub. Github.com. Процитовано 25 січня 2013.[недоступне посилання з лютого 2019]
Література
ред.- Halloway, Stuart (21 травня 2009), Programming Clojure (вид. 1st), Pragmatic Bookshelf, с. 304, ISBN 1934356336
- VanderHart, Luke; Sierra, Stuart (7 червня 2010), Practical Clojure (вид. 1st), Apress, с. 232, ISBN 1430272317, архів оригіналу за 17 вересня 2010, процитовано 1 березня 2011
- Fogus, Michael; Houser, Chris (28 грудня 2010), The Joy of Clojure (вид. 1st), Manning, с. 300, ISBN 1935182641, архів оригіналу за 4 січня 2011, процитовано 1 березня 2011
- Rathore, Amit (28 березня 2011), Clojure in Action (вид. 1st), Manning, с. 475, ISBN 1935182595, архів оригіналу за 11 березня 2011, процитовано 1 березня 2011
Посилання
ред.- Офіційний сайт
- GitHub code repository for Clojure [Архівовано 5 жовтня 2010 у Wayback Machine.]
- A comprehensive overview of Clojure [Архівовано 28 червня 2011 у Wayback Machine.]
- An overview of Clojure 1.2 in reference format [Архівовано 28 квітня 2017 у Wayback Machine.]
- Full Disclojure — Screencast [Архівовано 27 листопада 2011 у Wayback Machine.]
- Clojure talks on Blip.tv
- clojuredocs.org — Community-powered documentation and examples [Архівовано 7 серпня 2011 у Wayback Machine.]