Перейти к основному содержимому

Первая программа на Clojure

Разработчику

Черновик статьи. Ниже — полноценный пошаговый маршрут: JVM, Clojure CLI, REPL, deps.edn, namespace и -main. Leiningen и Calva упомянуты для IDE.

ШагТемаЗачем
1JVM и Clojure CLIСреда готова
2REPLПервый контакт со скобками
3Файл и namespaceОрганизация кода
4deps.ednЗависимости
5-main и запускПрограмма из терминала
6Java 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Каталоги исходников
:depsMaven-координаты зависимостей
: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:

  1. Установите расширение Calva.
  2. Откройте hello-clj, команда "Calva: Start a Project REPL".
  3. 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.corens hello.core
Unmatched delimiterЛишняя скобкаParedit / Calva rainbow
Slow first startJVM cold startНорма; GraalVM native для CLI отдельно
Unable to resolve symbolTypo или нет requireПроверить ns и :deps

Что дополнится в полной версии

  1. Тесты с clojure.test и alias :test.
  2. babashka для быстрых скриптов без JVM warm-up.
  3. Uberjar через tools.build.
  4. 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

Leiningendeps.edn + CLI
Файлproject.cljdeps.edn
Команда newlein new appclojure -Tnew app
Uberjarlein uberjartools.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 symbolNamespace not required
EOF while readingUnbalanced parens
ClassNotFoundExceptionMissing 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 -Sdescribe works
  • Hello from -M -e
  • Project with deps.edn and -main
  • One clojure.test green

Шаг 11 — babashka script

bb -e '(println "Fast script without JVM warm-up")'

Для CI glue — Bash alternative с Clojure syntax.


Troubleshooting

СимптомПричинаРешение
Unmatched )TypoCalva structural editing
Slow first clojureJVM cold startNormal; use babashka for scripts
Class not found interopWrong import(import '[java.util Date])
nREPL connection refusedFirewall/portCheck 7888, localhost
deps download failNetwork/MavenProxy settings, retry
Namespace file mismatchPath wrongsrc/foo/bar.cljfoo.bar

Упражнения

  1. REPL: define add, multiply, compose with comp.
  2. filter users map where :score > 50.
  3. Add Guava dep and call one static method.
  4. Write core_test.clj with two deftest.
  5. Start nREPL and connect Calva.
  6. reduce sum of vector without + direct on vector.
  7. Threading macro -> vs ->> on same data.
  8. Java System/getProperty "java.version".
  9. Create second namespace and require it.
  10. Compare Leiningen lein new output 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 -version OK
  • REPL: (+ 1 2) и println
  • hello-clj с deps.edn и -main
  • Java interop пример с Guava или JDK class
  • clojure -M:test green
  • nREPL или Calva eval одной формы