Clojure

REPLでのプログラミング:基本的な使用方法

Clojure式の評価

REPLを起動したら(前の章で説明したように)、Clojure式をREPLに入力してEnterキーを押すだけで評価できます。

user=> (+ 2 3)
5
user=> (defn factorial [n]
(if (= n 0)
  1
  (* n (factorial (dec n)))))
#'user/factorial
user=> (factorial 10)
3628800
user=>

各式の結果には、式の評価結果が表示されます。これがREPLの機能です。REPLは、送信された式を**R**ead(読み込み)、**E**valuate(評価)、**P**rint(出力)し、これを**L**oop(ループ)で繰り返します。

Clojureの学習中であれば、REPLで実験する時間をとってください。REPLが提供する迅速なフィードバックループは、非常に効果的な学習環境となります。

上記の例は非常に基本的なものですが、この方法でフル機能のClojureプログラムを実行できます。Clojureは、REPL環境が言語の全機能を提供するように設計されています。既存のClojureプログラムであれば、ソースファイルの内容を正しい順序でREPLに貼り付けるだけで実行できます。

ヒント:REPLの隣でエディタを使用する

ターミナルウィンドウ内でClojureコードを編集するのは面倒な場合があります。その場合は、構文認識Clojureモードを持つ任意のテキストエディタでコードを作成し、エディタからREPLターミナルウィンドウにコードをコピーアンドペーストするという簡単な方法があります。これがその例です(使用しているエディタはAtomです)。

Editor next to CLI REPL

このガイドのREPLワークフローの改善の章では、REPLを使用するためのより人間工学的な構成を見ていきます。ただし、この最小限の設定は、このチュートリアルの範囲には十分であり、基礎を習得するために重要です。

2種類の出力

次の評価を考えてみましょう。

user=> (println "Hello World")
Hello World
nil

これは奇妙です。以前の例とは異なり、(println "Hello World")式の評価では、Hello Worldnilの2つの結果が得られたように見えます。

これは、println関数が引数を標準出力に出力するが、nil返すためです。したがって、式の下に表示される2行は性質が大きく異なります。

  • Hello Worldは、式を評価することによる副作用(標準出力への出力)です。出力はコードによって行われました。

  • nilは、式を評価した結果です。出力はREPLによって行われました。

REPLからのClojureライブラリの呼び出し

これまで、REPLで手動で定義したコード(上記のfactorial関数など)のみを呼び出していました。しかし、REPLでは、既存のClojureコード、つまりClojure ライブラリも使用できます。[1] 名前空間がmy.name.spaceであるClojureライブラリの場合、(require '[my.name.space]) を評価することで、そのライブラリのコードをロードしてREPLで使用できます。

例:clojure.stringの使用

たとえば、clojure.stringは、テキストを操作するためのClojureにバンドルされているライブラリです。clojure.stringをrequireし、そのclojure.string/upper-case関数を呼び出してみましょう。

user=> (require '[clojure.string])
nil
user=> (clojure.string/upper-case "clojure")
"CLOJURE"

requireでは、:as句を追加することで、clojure.string名前空間のエイリアスを定義することもできます。これにより、clojure.string名前空間に定義されている名前をより簡潔に参照できます。

user=> (require '[clojure.string :as str])
nil
user=> (str/upper-case "clojure")
"CLOJURE"

最後に、非常に面倒くさがりでエイリアスをまったく入力したくない場合は、:refer句を追加できます。

user=> (require '[clojure.string :refer [upper-case]])
nil
user=> (upper-case "clojure")
"CLOJURE"

ドキュメントの参照

REPLは、clojure.replライブラリを使用して、APIドキュメントを参照するためにも使用できます。REPLで次の式を評価します。

user=> (require '[clojure.repl :refer :all])
nil

この式により、clojure.repl名前空間に定義されているすべての名前をREPLで使用できるようになります。

doc

(doc MY-VAR-NAME)を評価することで、指定されたVarのAPIドキュメントを出力できます。

user=> (doc nil?)
-------------------------
clojure.core/nil?
([x])
  Returns true if x is nil, false otherwise.
nil
user=> (doc clojure.string/upper-case)
-------------------------
clojure.string/upper-case
([s])
  Converts string to all upper-case.
nil

source

sourceを使用して、Varの定義に使用されたソースコードを表示することもできます。

user=> (source some?)
(defn some?
  "Returns true if x is not nil, false otherwise."
  {:tag Boolean
   :added "1.6"
   :static true}
  [x] (not (nil? x)))
nil

dir

dirを使用して、指定された名前空間に定義されているすべてのVarの名前を一覧表示できます。clojure.string名前空間でこれを実行してみましょう。

user=> (dir clojure.string)
blank?
capitalize
ends-with?
escape
includes?
index-of
join
last-index-of
lower-case
re-quote-replacement
replace
replace-first
reverse
split
split-lines
starts-with?
trim
trim-newline
triml
trimr
upper-case
nil

別の例として、dirを使用してclojure.repl自体で使用できるものを確認してみましょう。

user=> (dir clojure.repl)
apropos
demunge
dir
dir-fn
doc
find-doc
pst
root-cause
set-break-handler!
source
source-fn
stack-element-str
thread-stopper
nil

これまでに使用したdocsourcedir操作が認識できます。

apropos

Varの名前を正確に覚えていない場合は、aproposを使用して検索できます。

user=> (apropos "index")
(clojure.core/indexed? clojure.core/keep-indexed clojure.core/map-indexed clojure.string/index-of clojure.string/last-index-of)

aproposはVar名のみを検索します。docによって出力されるテキスト(docstring))を検索するには、find-docを使用します。

find-doc

user=> (find-doc "indexed")
-------------------------
clojure.core/contains?
([coll key])
 Returns true if key is present in the given collection, otherwise
 returns false.  Note that for numerically indexed collections like
 vectors and Java arrays, this tests if the numeric key is within the
 range of indexes. 'contains?' operates constant or logarithmic time;
 it will not perform a linear search for a value.  See also 'some'.
-------------------------
clojure.core/indexed?
([coll])
 Return true if coll implements Indexed, indicating efficient lookup by index
-------------------------
clojure.core/keep-indexed
([f] [f coll])
 Returns a lazy sequence of the non-nil results of (f index item). Note,
 this means false return values will be included.  f must be free of
 side-effects.  Returns a stateful transducer when no collection is
 provided.
-------------------------
clojure.core/map-indexed
([f] [f coll])
 Returns a lazy sequence consisting of the result of applying f to 0
 and the first item of coll, followed by applying f to 1 and the second
 item in coll, etc, until coll is exhausted. Thus function f should
 accept 2 arguments, index and item. Returns a stateful transducer when
 no collection is provided.
nil

ドキュメントは、requireされたライブラリについてのみ使用できます。

たとえば、clojure.set名前空間をrequireしていない場合、clojure.set/unionのドキュメントを検索することはできません。このREPLセッションの例で示されています。

clj
Clojure 1.10.0
user=> (doc clojure.set/union)
nil                             ;; no doc found
user=> (apropos "union")
()
user=> (require '[clojure.set]) ;; now we're requiring clojure.set
nil
user=> (doc clojure.set/union)
-------------------------
clojure.set/union
([] [s1] [s1 s2] [s1 s2 & sets])
  Return a set that is the union of the input sets
nil
user=> (apropos "union")
(clojure.set/union)
user=>

1. Clojure ライブラリと呼んでいるものが、必ずしもライブラリであるとは限りません。現在のプロジェクトのソースコードファイルの場合もあります。