Clojure

Deps および CLI ガイド

Clojure は、以下のためのコマンドラインツールを提供します。

  • インタラクティブ REPL (Read-Eval-Print Loop) の実行

  • Clojure プログラムの実行

  • Clojure 式の評価

上記のすべてのシナリオで、他の Clojure および Java ライブラリ(依存関係または「deps」)を使用したい場合があります。これらは、ローカルで作成しているライブラリ、git(例:GitHub)にあるプロジェクト、または一般的には、Maven エコシステムで利用可能であり、Maven Central や Clojars などの中央リポジトリでホストされているライブラリである可能性があります。

すべての場合において、ライブラリを使用するには、

  1. 使用したいライブラリを、名前やバージョンなどの側面を指定して指定する必要があります。

  2. git または Maven リポジトリから(一度だけ)ローカルマシンに取得する必要があります。

  3. REPL またはプログラムの実行中に Clojure が見つけられるように、JVM クラスパスで使用できるようにする必要があります。

Clojure ツールは、(a) の構文とファイル (deps.edn) を指定し、それに基づいて (b) と (c) を自動的に処理します。

ツールのインストール方法の詳細については、入門を参照してください。ここでは、入門方法について説明します。完全なリファレンスについては、Clojure CLIdeps.edn を参照してください。バージョン情報については、変更履歴を参照してください。

REPL の実行とライブラリの使用

ツールをダウンロードしてインストールしたら、clj ツールを実行して REPL を起動できます。

$ clj
Clojure 1.11.2
user=>

REPL で Clojure 式を入力して Enter キーを押すと、式が評価されます。REPL を終了するには、Control-D を押します。

user=> (+ 2 3)   # press the `enter` key after typing the expression to evaluate it
5                # result of expression
user=>           # type Ctrl-D here to exit the REPL (does not print)
$

必要になる可能性のあるほぼすべての機能へのアクセスを提供する、多くの Clojure および Java ライブラリが利用可能です。たとえば、日付と時刻を操作するための一般的な Clojure ライブラリ clojure.java-time を考えてみましょう。

このライブラリを使用するには、ツールがダウンロードされ、クラスパスに追加されるように、依存関係として宣言する必要があります。ほとんどのプロジェクトの README に、使用する名前とバージョンが示されています。deps.edn ファイルを作成して、依存関係を宣言します。

{:deps
 {clojure.java-time/clojure.java-time {:mvn/version "1.1.0"}}}

または、バージョンがわからない場合は、find-versions ツールを使用すると、利用可能なすべての座標がソートされた順序で一覧表示されます。

$ clj -X:deps find-versions :lib clojure.java-time/clojure.java-time
...omitted
{:mvn/version "1.0.0"}
{:mvn/version "1.1.0"}

clj ツールを使用して REPL を再起動します。

$ clj
Downloading: clojure/java-time/clojure.java-time/1.1.0/clojure.java-time-1.1.0.pom from clojars
Downloading: clojure/java-time/clojure.java-time/1.1.0/clojure.java-time-1.1.0.jar from clojars
Clojure 1.11.2
user=> (require '[java-time.api :as t])
nil
user=> (str (t/instant))
"2022-10-07T16:06:50.067221Z"

依存関係を初めて使用するときに、ライブラリがダウンロードされているというメッセージが表示されます。ファイルがダウンロードされると (通常は ~/.m2 または ~/.gitlibs に)、今後再利用されます。同じプロセスを使用して、他のライブラリを deps.edn ファイルに追加し、Clojure または Java ライブラリを調べることができます。

プログラムの作成

すぐに、これらのライブラリを使用する独自のコードを構築して保存したくなるでしょう。新しいディレクトリを作成し、この deps.edn をコピーします。

$ mkdir hello-world
$ cp deps.edn hello-world
$ cd hello-world
$ mkdir src

デフォルトでは、clj ツールは src ディレクトリにあるソースファイルを検索します。src/hello.clj を作成します。

(ns hello
  (:require [java-time.api :as t]))

(defn time-str
  "Returns a string representation of a datetime in the local time zone."
  [instant]
  (t/format
    (t/with-zone (t/formatter "hh:mm a") (t/zone-id))
    instant))

(defn run [opts]
  (println "Hello world, the time is" (time-str (t/instant))))

メインの使用

このプログラムには、-X を使用して clj によって実行できるエントリ関数 run があります。

$ clj -X hello/run
Hello world, the time is 12:19 PM

ローカルライブラリの使用

このアプリケーションの一部をライブラリに移動することに決めるかもしれません。clj ツールは、ローカルディスクにのみ存在するプロジェクトをサポートするためにローカル座標を使用します。このアプリケーションの java-time 部分を、並列ディレクトリ time-lib 内のライブラリに抽出しましょう。最終的な構造は次のようになります。

├── time-lib
│   ├── deps.edn
│   └── src
│       └── hello_time.clj
└── hello-world
    ├── deps.edn
    └── src
        └── hello.clj

time-lib の下に、すでに持っている deps.edn ファイルのコピーを使用し、ファイル src/hello_time.clj を作成します。

(ns hello-time
  (:require [java-time.api :as t]))

(defn now
  "Returns the current datetime"
  []
  (t/instant))

(defn time-str
  "Returns a string representation of a datetime in the local time zone."
  [instant]
  (t/format
    (t/with-zone (t/formatter "hh:mm a") (t/zone-id))
    instant))

hello-world/src/hello.clj のアプリケーションを更新して、代わりにライブラリを使用します。

(ns hello
  (:require [hello-time :as ht]))

(defn run [opts]
  (println "Hello world, the time is" (ht/time-str (ht/now))))

hello-world/deps.edn を変更して、time-lib ライブラリのルートディレクトリを参照するローカル座標を使用します (自分のマシンのパスを必ず更新してください)。

{:deps
 {time-lib/time-lib {:local/root "../time-lib"}}}

次に、アプリケーションを実行して、hello-world ディレクトリからすべてをテストできます。

$ clj -X hello/run
Hello world, the time is 12:22 PM

git ライブラリの使用

そのライブラリを他の人と共有できれば素晴らしいでしょう。プロジェクトをパブリックまたはプライベート git リポジトリにプッシュし、他の人が git 依存関係座標で使用できるようにすることで、これを実現できます。

まず、time-lib の git ライブラリを作成します。

cd ../time-lib
git init
git add deps.edn src
git commit -m 'init'

次に、パブリック git リポジトリホスト (GitHub など) に移動し、この git リポジトリを作成および公開するための指示に従います。

このリリースに意味のあるバージョンを持たせるために、タグを付けます。

git tag -a 'v0.0.1' -m 'initial release'
git push --tags

最後に、git 依存関係を代わりに使用するようにアプリを変更します。次の情報を収集する必要があります。

  • リポジトリライブラリ - Clojure CLI は、GitHub URL https://github.com/yourname/time-lib.git に対して io.github.yourname/time-lib のようなライブラリ名を使用する場合、URL を指定する必要がないという規則を使用します。

  • タグ - 上記で作成した v0.0.1 です。

  • sha - タグの短い sha です。リポジトリがローカルにある場合は git rev-parse --short v0.0.1^{commit} で、リモートにある場合は git ls-remote https://github.com/yourname/time-lib.git v0.0.1 で見つけることができます。GitHub リポジトリを使用してタグとそのバッキングコミットを調べることもできます。

hello-world/deps.edn を更新して、git 座標を代わりに使用します。

{:deps
 {io.github.yourname/time-lib {:git/tag "v0.0.1" :git/sha "4c4a34d"}}}

これで、(共有) git リポジトリライブラリを使用して、アプリを再度実行できます。最初に実行すると、clj がリポジトリとコミット作業ツリーをダウンロードしてキャッシュするときに、コンソールに追加のメッセージが表示されます。

$ clj -X hello/run
Cloning: https://github.com/yourname/time-lib
Checking out: https://github.com/yourname/time-lib at 4c4a34d
Hello world, the time is 02:10 PM

これで、友達も time-lib を使用できます!

その他の例

プログラムがより複雑になるにつれて、標準のクラスパスのバリエーションを作成する必要がある場合があります。Clojure ツールは、エイリアスを使用してクラスパスの変更をサポートします。これは、対応するエイリアスが提供された場合にのみ使用される deps ファイルの一部です。実行できることのいくつかを紹介します。

テストソースディレクトリを含める

通常、プロジェクトのクラスパスには、デフォルトではプロジェクトのソースのみが含まれ、テストソースは含まれません。クラスパスの構築のクラスパス作成ステップで、プライマリクラスパスへの変更として追加のパスを追加できます。そのためには、追加の相対ソースパス "test" を含むエイリアス :test を追加します。

{:deps
 {org.clojure/core.async {:mvn/version "1.3.610"}}

 :aliases
 {:test {:extra-paths ["test"]}}}

そのクラスパスの変更を適用し、clj -A:test -Spath を呼び出して変更されたクラスパスを調べます。

$ clj -A:test -Spath
test:
src:
/Users/me/.m2/repository/org/clojure/clojure/1.11.2/clojure-1.11.2.jar:
... same as before (split here for readability)

これで、テストディレクトリがクラスパスに含まれるようになりました。

テストランナーを使用してすべてのテストを実行する

前のセクションの :test エイリアスを拡張して、すべての clojure.test テストを実行するための cognitect-labs テストランナーを含めることができます。

:test エイリアスを拡張します。

{:deps
 {org.clojure/core.async {:mvn/version "1.3.610"}}

 :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-fn cognitect.test-runner.api/test}}}

次に、デフォルト設定を使用してテストランナーを実行します (test/ ディレクトリの下の -test 名前空間のすべてのテストを実行します)。

clj -X:test

オプションの依存関係を追加する

deps.edn ファイルのエイリアスを使用して、クラスパスに影響するオプションの依存関係を追加することもできます。

{:aliases
 {:bench {:extra-deps {criterium/criterium {:mvn/version "0.4.4"}}}}}

ここでは、:bench エイリアスを使用して、追加の依存関係、つまり criterium ベンチマークライブラリを追加しています。

:bench エイリアスを追加して、依存関係の解決を変更することで、この依存関係をクラスパスに追加できます。clj -A:bench

コマンドラインから依存関係を追加する

既存の deps.edn ファイルに追加したり、ファイルを作成したりせずにライブラリを試してみるのに役立つ場合があります。

$ clojure -Sdeps '{:deps {org.clojure/core.async {:mvn/version "1.5.648"}}}'
Clojure 1.11.2
user=> (require '[clojure.core.async :as a])
nil

エスケープ規則のため、構成データを一重引用符で囲むのが最善であることに注意してください。

ソース依存関係ライブラリの準備

一部の依存関係では、クラスパスで使用する前に準備ステップが必要になります。これらのライブラリは、deps.edn でこの必要性を明記する必要があります。

{:paths ["src" "target/classes"]
 :deps/prep-lib {:alias :build
                 :fn compile
                 :ensure "target/classes"}}

トップレベルキー :deps/prep-lib を含めると、tools.deps クラスパス構築に、このライブラリを準備するために追加が必要であり、:build エイリアスで compile 関数を呼び出すことで実行できることが通知されます。準備ステップが完了したら、パス "target/classes" を作成する必要があります。これは完了をチェックできます。

他のソースベースのライブラリ (git またはローカルの場合があります) と同じように、このライブラリに依存します。

{:deps {my/lib {:local/root "../needs-prep"}}}

次に、そのライブラリをクラスパスに含めようとすると、エラーが表示されます。

$ clj
Error building classpath. The following libs must be prepared before use: [my/lib]

次に、このコマンドを使用して CLI に準備するように指示できます (これは、特定のライブラリバージョンに対する 1 回限りのアクションです)。

$ clj -X:deps prep
Prepping io.github.puredanger/cool-lib in /Users/me/demo/needs-prep
$ clj
Clojure 1.11.2
user=>

依存関係をオーバーライドする

複数のエイリアスを組み合わせて使用できます。たとえば、この deps.edn ファイルは、古い core.async バージョンの使用を強制する :old-async と、追加の依存関係を追加する :bench の 2 つのエイリアスを定義します。

{:deps
 {org.clojure/core.async {:mvn/version "0.3.465"}}

 :aliases
 {:old-async {:override-deps {org.clojure/core.async {:mvn/version "0.3.426"}}}
  :bench {:extra-deps {criterium/criterium {:mvn/version "0.4.4"}}}}}

次のように両方のエイリアスをアクティブにします。clj -A:bench:old-async

ディスク上のローカルjarを含める

データベースドライバのjarなど、Mavenリポジトリに存在しないディスク上のjarを直接参照する必要がある場合があります。

ローカルjarの依存関係は、ディレクトリではなくjarファイルを直接指すローカル座標で指定します。

{:deps
 {db/driver {:local/root "/path/to/db/driver.jar"}}}

事前(AOT)コンパイル

gen-classまたはgen-interfaceを使用する場合、Javaクラスを生成するために、Clojureソースを事前にコンパイルする必要があります。

これはcompileを呼び出すことで実行できます。コンパイルされたクラスファイルのデフォルトの出力先はclasses/であり、作成してクラスパスに追加する必要があります。

$ mkdir classes

deps.ednを編集して、パスに"classes"を追加します。

{:paths ["src" "classes"]}

src/my_class.cljでgen-classを使用してクラスを宣言します。

(ns my-class)

(gen-class
  :name my_class.MyClass
  :methods [[hello [] String]])

(defn -hello [this]
  "Hello, World!")

次に、別のソースファイルsrc/hello.clj:importを使用してクラスを参照できます。名前空間も:requireに追加されていることに注意してください。これにより、コンパイル時に依存するすべての名前空間が自動的に検出されてコンパイルされます。

(ns hello
  (:require [my-class])
  (:import (my_class MyClass)))

(defn -main [& args]
  (let [inst (MyClass.)]
    (println (.hello inst))))

コンパイルはREPLで実行するか、スクリプトを実行して行うことができます。

$ clj -M -e "(compile 'hello)"

その後、hello名前空間を実行します。

$ clj -M -m hello
Hello, World!

完全なリファレンスについては、コンパイルとクラス生成を参照してください。

ソケットサーバーのリモートREPLを実行する

Clojureは、ソケットサーバーの実行、特にそれらを使用してリモートREPLをホストするための組み込みサポートを提供しています。

ソケットサーバーREPLを構成するには、次の基本構成をdeps.ednに追加します。

{:aliases
 {:repl-server
  {:exec-fn clojure.core.server/start-server
   :exec-args {:name "repl-server"
               :port 5555
               :accept clojure.core.server/repl
               :server-daemon false}}}}

そして、エイリアスで呼び出すことでサーバーを起動します。

clojure -X:repl-server

必要に応じて、コマンドラインでデフォルトのパラメータをオーバーライドしたり(または追加のオプションを追加したり)することもできます。

clojure -X:repl-server :port 51234

別のターミナルから接続するには、netcatを使用できます。

nc localhost 51234
user=> (+ 1 1)
2

REPLを終了するにはCtrl-D、サーバーを終了するにはCtrl-Cを使用します。

すべての依存関係をリストする

組み込みの:depsエイリアスには、プロジェクトで使用される推移的な依存関係の完全なセット(およびそのライセンス)を調べるのに役立つツールがいくつかあります。

クラスパスに含まれるすべての依存関係の完全なセットをリストするには、clj -X:deps listを使用します。たとえば、このガイドの冒頭にあるhello-worldアプリケーションでは、次のようなものが表示されます。

% clj -X:deps list
clojure.java-time/clojure.java-time 1.1.0  (MIT)
org.clojure/clojure 1.11.2  (EPL-1.0)
org.clojure/core.specs.alpha 0.2.62  (EPL-1.0)
org.clojure/spec.alpha 0.3.218  (EPL-1.0)
time-lib/time-lib ../cli-getting-started/time-lib

アプリケーションで使用される推移的な依存関係の完全なセットが、バージョンとライセンスとともにアルファベット順にリストされます。追加の印刷オプションについては、APIドキュメントを参照してください。

依存関係のツリー構造と、バージョンの選択がどのように行われたかを理解したい場合は、clj -X:deps treeを使用します。

% clj -X:deps tree
org.clojure/clojure 1.11.2
  . org.clojure/spec.alpha 0.3.218
  . org.clojure/core.specs.alpha 0.2.62
time-lib/time-lib /Users/alex.miller/tmp/cli-getting-started/time-lib
  . clojure.java-time/clojure.java-time 1.1.0

ここではバージョンの選択は行われていませんが、必要に応じてツリーで選択がどのように説明されるかについて詳しくは、ドキュメントを参照してください。

これらのヘルパー関数はどちらも、clj -X:deps list :aliases '[:alias1 :alias2]'のように、1つまたは複数のエイリアスを適用して依存関係のリストまたはツリーを調べたい場合に、オプションの:aliases引数を取ります。

オリジナル著者: Alex Miller