Первая программа на Clojure
Черновик статьи. Ниже — полноценный пошаговый маршрут: JVM, Clojure CLI, REPL,
deps.edn, namespace и-main. Leiningen и Calva упомянуты для IDE.
| Шаг | Тема | Зачем |
|---|---|---|
| 1 | JVM и Clojure CLI | Среда готова |
| 2 | REPL | Первый контакт со скобками |
| 3 | Файл и namespace | Организация кода |
| 4 | deps.edn | Зависимости |
| 5 | -main и запуск | Программа из терминала |
| 6 | Java interop | Библиотека с Maven |
База по Java — Java — о разделе. Контекст языка — история.
Шаг 1 — подготовка
JVM
Clojure требует JVM (JDK). Рекомендуется Temurin 17 или 21:
java -version
Windows — установщик с adoptium.net; Linux — пакет temurin-17-jdk; macOS — Homebrew brew install temurin.
Clojure CLI
# Linux/macOS — см. https://clojure.org/guides/install_clojure
curl -O https://download.clojure.org/install/linux-install-1.12.0.1488.sh
chmod +x linux-install-1.12.0.1488.sh
sudo ./linux-install-1.12.0.1488.sh
clojure -version
Windows — установщик с clojure.org или winget install Clojure.Clojure.
Проверка:
clojure -version
# Clojure CLI version 1.12.x ...
Шаг 2 — REPL, первый контакт
REPL (Read-Eval-Print Loop) — интерактивная консоль: вводите выражение, сразу видите результат.
clojure
(+ 1 2 3)
;; => 6
(println "Привет, Clojure!")
;; Привет, Clojure!
;; => nil
(str "Hello, " "World")
;; => "Hello, World"
Разбор:
- Выражения в круглых скобках — вызов функции; оператор
+— тоже функция. - Первое значение после
(— функция, остальное — аргументы. printlnвозвращаетnil(какvoid); side effect — вывод в консоль.- Комментарий строки —
;, блок —#_(...).
Выход из REPL: Ctrl+D или (quit).
Полезные REPL-команды
| Ввод | Действие |
|---|---|
(doc +) | Документация функции (нужен classpath) |
(source +) | Исходник, если доступен |
(dir clojure.core) | Список public vars |
*1, *2, *3 | Последние три результата |
REPL — основной инструмент разработки; файл .clj подключают по мере роста проекта.
Шаг 3 — структура проекта
Создайте каталог hello-clj:
hello-clj/
deps.edn
src/
hello/
core.clj
src/hello/core.clj:
(ns hello.core)
(defn greet [name]
(str "Привет, " name "!"))
(defn -main [& _args]
(println (greet "мир")))
Разбор:
(ns hello.core)— namespace; путь файлаsrc/hello/core.cljсоответствуетhello.core.defn— define function.-main— точка входа дляclojure -M -m hello.core.[& _args]— variadic args, не используем (префикс_).
Шаг 4 — deps.edn
deps.edn в корне проекта:
{:paths ["src"]
:deps {org.clojure/clojure {:mvn/version "1.12.0"}}
:aliases {:run {:main-opts ["-m" "hello.core"]}}}
| Ключ | Смысл |
|---|---|
:paths | Каталоги исходников |
:deps | Maven-координаты зависимостей |
:aliases | Именованные профили (:run, :test) |
Запуск:
cd hello-clj
clojure -M:run
# или
clojure -M -m hello.core
Ожидаемый вывод: Привет, мир!
Шаг 5 — коллекции и функции
В REPL или файле:
(def users [{:id 1 :name "Anna"} {:id 2 :name "Bob"}])
(map :name users)
;; => ("Anna" "Bob")
(filter #(> (:id %) 1) users)
;; => ({:id 2, :name "Bob"})
(into [] (map :name users))
;; => ["Anna" "Bob"]
| Тип | Лiteral | Пример |
|---|---|---|
| vector | [ ] | [1 2 3] |
| map | { } | {:a 1 :b 2} |
| set | #{ } | #{1 2 3} |
| list | ( ) | '(1 2 3) — quoted |
| string | " " | "hello" |
Keyword :name — ключ map; (map :name users) — shorthand для (map #(get % :name) users).
Шаг 6 — Java interop
Добавьте зависимость в deps.edn:
:deps {org.clojure/clojure {:mvn/version "1.12.0"}
com.google.guava/guava {:mvn/version "33.3.1-jre"}}
В core.clj:
(import '[com.google.common.base Joiner])
(defn join-names [names]
(.join (Joiner/on ", ") names))
(println (join-names ["Anna" "Bob"]))
Синтаксис interop:
Class/method— static;(.method obj args)— instance;importвns— как в Java.
Любой JAR с Maven Central подключается через :deps.
Шаг 7 — IDE (Calva)
Calva — расширение VS Code для Clojure:
- Установите расширение Calva.
- Откройте
hello-clj, команда "Calva: Start a Project REPL". - Eval текущей формы —
Ctrl+Enter.
Cursive — плагин IntelliJ IDEA (Ultimate или Community с ограничениями). Emacs CIDER — классический выбор в Lisp-сообществе.
nREPL позволяет eval из редактора без перезапуска JVM.
Leiningen (альтернатива)
Legacy и учебные проекты часто на Leiningen:
lein new app hello-lein
cd hello-lein
lein repl
lein run
project.clj аналог deps.edn. Новые проекты чаще начинают с Clojure CLI — см. о разделе.
Типичные ошибки
| Симптом | Причина | Решение |
|---|---|---|
java not found | Нет JDK | Установить Temurin |
Could not find or load main class | Неверный -m namespace | -m hello.core ↔ ns hello.core |
| Unmatched delimiter | Лишняя скобка | Paredit / Calva rainbow |
| Slow first start | JVM cold start | Норма; GraalVM native для CLI отдельно |
Unable to resolve symbol | Typo или нет require | Проверить ns и :deps |
Что дополнится в полной версии
- Тесты с clojure.test и alias
:test. - babashka для быстрых скриптов без JVM warm-up.
- Uberjar через
tools.build. - nREPL remote и Docker devcontainer.
См. также: о разделе · история · Lisp — о разделе.
В REPL определите greet, затем перенесите в core.clj и запустите clojure -M:run. Добавьте map пользователей и выведите имена через map и println.
Второй проход — тесты и babashka (черновик)
clojure.test
test/hello_test.clj:
(ns hello-test
(:require [clojure.test :refer :all]
[hello.core :refer [greet]]))
(deftest greet-test
(is (= "Привет, мир!" (greet "мир"))))
deps.edn:
:aliases {:test {:extra-paths ["test"]
:extra-deps {io.github.cognitect-labs/test-runner
{:git/tag "v0.5.1" :git/sha "dfb30dd"}}
:main-opts ["-m" "cognitect.test-runner"]}}}
clojure -M:test
babashka (скрипты без JVM warm-up)
babashka — нативный бинарник Clojure subset для CLI:
bb -e '(+ 1 2 3)'
Для длинных JVM-старта в CI-скриптах — альтернатива clojure -M. Полная статья в плане раздела.
Форматирование
cljfmt или clojure-lsp format в VS Code — единый стиль скобок в команде. Paredit в Calva предотвращает unmatched delimiter.
Шаг 8 — namespace и require
src/hello/util.clj:
(ns hello.util)
(defn capitalize-name [s]
(clojure.string/capitalize s))
В core.clj:
(ns hello.core
(:require [hello.util :as util]))
(defn -main [& _args]
(println (util/capitalize-name "clojure")))
:require — аналог import; alias :as util сокращает префикс.
Шаг 9 — clojure.test
test/hello/core_test.clj:
(ns hello.core-test
(:require [clojure.test :refer :all]
[hello.core :refer [greet]]))
(deftest greet-test
(is (= "Привет, Anna!" (greet "Anna"))))
deps.edn:
:aliases {:test {:extra-paths ["test"]
:extra-deps {io.github.cognitect-labs/test-runner
{:git/tag "v0.5.1" :git/sha "dfb30dd"}}
:main-opts ["-m" "cognitect.test-runner"]
:exec-args {:dirs ["test"]}}}
clojure -M:test
Шаг 10 — nREPL (обзор)
clojure -Sdeps '{:deps {nrepl/nrepl {:mvn/version "1.3.0"}}}' \
-M -m nrepl.cmdline --port 7888
Calva подключается к порту 7888 — eval из редактора без перезапуска JVM.
Сравнение Leiningen и deps.edn
| Leiningen | deps.edn + CLI | |
|---|---|---|
| Файл | project.clj | deps.edn |
| Команда new | lein new app | clojure -Tnew app |
| Uberjar | lein uberjar | tools.build |
| Зрелость | legacy projects | новые проекты |
Шаг — uberjar через tools.build
;; build.clj (упрощённо)
(defn -main [& _]
(b/uber {:class-dir "target/classes"
:uber-file "target/app.jar"
:basis (b/create-basis {:project "deps.edn"})}))
Запуск: clojure -T:build uber. JAR деплоится как java -jar app.jar.
Шаг — namespace и require
(ns myapp.core
(:require [clojure.string :as str]))
(defn slug [s]
(str/lower-case (str/replace s #"\\s+" "-")))
Файл src/myapp/core.clj соответствует namespace myapp.core.
Шаг — тесты с clojure.test
(ns myapp.core-test
(:require [clojure.test :refer :all]
[myapp.core :refer [slug]]))
(deftest slug-test
(is (= "hello-world" (slug "Hello World"))))
Запуск: clojure -M:test при alias :test в deps.edn.
Типичные ошибки REPL
| Ошибка | Причина |
|---|---|
| Unable to resolve symbol | Namespace not required |
| EOF while reading | Unbalanced parens |
| ClassNotFoundException | Missing dep in deps.edn |
FAQ (первая программа)
JDK version? 17+ LTS recommended.
Windows path spaces? Quote paths in -Sdeps.
IDE without nREPL? Plain clojure REPL enough for start.
Commit deps.edn? Yes; lock git SHA for git deps.
Чек-лист
-
clojure -Sdescribeworks - Hello from
-M -e - Project with
deps.ednand-main - One
clojure.testgreen
Шаг 11 — babashka script
bb -e '(println "Fast script without JVM warm-up")'
Для CI glue — Bash alternative с Clojure syntax.
Troubleshooting
| Симптом | Причина | Решение |
|---|---|---|
Unmatched ) | Typo | Calva structural editing |
Slow first clojure | JVM cold start | Normal; use babashka for scripts |
| Class not found interop | Wrong import | (import '[java.util Date]) |
| nREPL connection refused | Firewall/port | Check 7888, localhost |
| deps download fail | Network/Maven | Proxy settings, retry |
| Namespace file mismatch | Path wrong | src/foo/bar.clj → foo.bar |
Упражнения
- REPL: define
add,multiply, compose withcomp. filterusers map where:score > 50.- Add Guava dep and call one static method.
- Write
core_test.cljwith twodeftest. - Start nREPL and connect Calva.
reducesum of vector without+direct on vector.- Threading macro
->vs->>on same data. - Java
System/getProperty"java.version". - Create second namespace and
requireit. - Compare Leiningen
lein newoutput with deps.edn project.
FAQ
Calva or Cursive? Calva free in VS Code; Cursive IntelliJ license.
Emacs required? No — Calva sufficient.
JDK vendor? Temurin 17 or 21 recommended.
Windows paths? Use forward slashes in deps.edn paths.
Docker devcontainer? Clojure CLI image + nREPL port forward.
Hot reload? nREPL eval namespace reload; not automatic file watch without tool.
Production memory? Tune JVM -Xmx; profile heap.
ClojureScript in same repo? Possible; separate build — chapter 8+.
SQL access? next.jdbc on JVM — future article.
Logging? clojure.tools.logging → SLF4J.
Format code? cljfmt, clojure-lsp.
Security deps.edn? Pin git SHA; audit jars.
Next? intro.md, 1.md, Java intro.
REPL workflow daily? Eval small functions first; commit when green tests.
Commit deps.edn only? Yes; lock via clojure -Spath hash or tools.deps alpha lock.
Связанные материалы
| Тема | Ссылка |
|---|---|
| История | 1.md |
| О разделе | intro.md |
| Java | /encyclopedia/5-languages/5-03-java/intro |
| Lisp | /encyclopedia/5-languages/5-16-starye-yazyki/Lisp/intro |
| Git | /encyclopedia/4-code-dev/4-13-osnovy-raboty-s-git/112 |
Шаги 1–13: JVM, REPL, deps.edn, interop, tests, nREPL. Дальше — concurrency (atoms) и macros в планируемых главах.
См. также: о разделе · история.
Итоговый чек-лист первой программы
-
java -versionиclojure -versionOK - REPL:
(+ 1 2)иprintln -
hello-cljсdeps.ednи-main - Java interop пример с Guava или JDK class
-
clojure -M:testgreen - nREPL или Calva eval одной формы