ClojureはProgramming Language、LISPの方言の1つ。 プログラムはJava仮想マシン、.NETで動作する。
- Javaのランタイム
- Leiningen…依存関係管理。
sudo apt-get install leiningen
lein repl
(println "hello world")
(defn hello [name] (str "Hello, " name))
(defn hello-world [username]
(println (format "Hello, %s" username)))
状態を共有する。 セットに要素を1つ追加する。
(conj coll item)
(conj #{} "Stu")
セットを知る。
conj
を update
-fn として渡す。
(def visitors (atom #{}))
(swap! visitors conj "Stu")
atom
はClojureが持つリファレンスタイプのひとつ。
ref
の中身は deref
で見ることができる。略記として @
を使える。
(deref visitors)
@visitors
(defn hello
"Writes hello message to *out*. Calls you by username.
Knows if you have been here before."
[username]
(swap! visitors conj username)
(str "Hello, " username))
(hello "Rich")
@visitors
状態の問題。それまでにどういう動作をしたかによって、結果が異なってくる。 純粋な関数なら、局所的な知識だけで動作を理解できるが、状態が絡むとそこに至るすべての履歴を理解しなければならない。
use
は require
と refer
を一度にやってくれる。
(require 'clojure.java.io)
(use 'clojure.java.io)
再読込。
(use :reload 'clojure.java.io)
(find-doc "reduce")
repl
ライブラリを使う。
(use 'clojure.repl)
(source identity)
ClojureのコレクションがJavaのコレクションであることを確かめる。
(ancestors (class [1 2 3]))
- Clojureは同図像性を持つ。
- ClojureのコードはClojureのデータによって組み立てられる。
- Clojureのプログラムを走らせるときには、まずClojureの中の
リーダ
と呼ばれる部分がプログラムをフォーム
と呼ばれる単位ごとに読み込んで、それをClojureのデータ構造へと変換する。そしてデータ構造をコンパイルして実行する。
フォームにはブール値、文字、キーワード、リスト、マップ…などがある。
シンボルは、Clojure内のあらゆる種類のものの名前として使う。
ドットでJavaメソッドの呼び出しであると判断する。
(.toUpperCase "hello")
(str "hello")
リテラル表記は \{letter}
。
(str \h \e \y \space \y \o \u)
文字を交互に結合。
(interleave "Attack at midnight" "The purple elephant chortled")
文字列に戻す。
(apply str (interleave "Attack at midnight" "The purple elephant chortled"))
復元する。
(apply str (take-nth 2 "ATthtea cpku raptl em iedlneipghhatn"))
Clojureでは空リストは偽ではない。
(if () "We are in Clojure!" "We are in Common Lisp!")
true?
は値が true
そのものであるかを調べる。
(true? true)
(true? "foo")
(zero? 0.0)
述語一覧。
(find-doc #"\?$")
マップ。
(def inventors {"Lisp" "McCarthy" "Clojure" "Hickey"})
みやすさのためにコロンを置くこともできる。
(def inventors {"Lisp" "McCarthy", "Clojure" "Hickey"})
マップは関数としても動作する。
(inventors "Lisp")
get
関数も使える。存在しない場合の値を指定できる。
(get inventors "Lisp" "not find")
(get inventors "Foo" "not find")
キーワード
はシンボルに似ているが、コロンで始まる。評価されると自分自身を返す。
:foo
シンボルとは異なる。シンボルは評価されるとそれに結び付けられた何かを返そうとする。
foo
キーワードをキーにして書き直す。
(def inventors {:Lisp "McCarthy" :Clojure "Hickey"})
defrecord
を使って構造体を定義することで、各マップがどういうキーを取りうるか明示し、強制できる。
(defrecord Book [title author])
(->Book "title" "author")
マップと同じように扱える。
(def b (->Book "Anathem" "Neal Stephenson"))
(:title b)
Clojureのフォームはリーダによって読まれて、テキストからClojureのデータに変換される。 リーダマクロは前置される特定のマクロ文字によって起動される、リーダの特殊な動作。 コメントのセミコロンや、評価抑制のクォートもリーダマクロ。
関数呼び出しは単に最初の要素が関数を指すようなリストである。
自分で関数を定義するには defn
を使う( def
ではない)。
(defn greeting
"Returns a greeting of the form 'Hello, username.'"
[username]
(str "Hello, " username))
(greeting "world")
同名の関数で受け取る引数が違うものを呼び出す。
ゼロ引数の greeting
は1引数の greeting
に渡して移譲できる。
(defn greeting
"Returns a greeting of the form 'Hello, username.'
Default username is 'world'."
([] (greeting "world"))
([username] (str "Hello, " username)))
(greeting)
フィルタ。 まず述語を定義する。
(defn indexable-word? [word]
(> (count word) 2))
適用する。
(require '[clojure.string :as str])
(filter indexable-word? (str/split "A fine day it is" #"\W+"))
無名関数バージョン。
(filter (fn [w] (> (count w) 2)) (str/split "A fine day" #"\W+"))
(defn make-greeter [greeting-prefix]
(fn [username] (str greeting-prefix ", " username)))
(def hello-greeting (make-greeter "Hello"))
(hello-greeting "world")
(def aloha-greeting (make-greeter "Aloha"))
(aloha-greeting "Hawaii")
名前を与えなくてもいい。
((make-greeter "Howdy") "pardner")
オブジェクトを def
defn
で定義すると、オブジェクトはClojureの var
に格納される。
リーダマクロで user/foo に結び付けられた var
を見る。
(def foo 10)
#'foo
実引数と仮引数のnumberが束縛される。
(defn triple [number] (* 3 number))
(triple 10)
関数の引数束縛はレキシカルスコープ…関数本体の中だけから見える。
コレクション全体を変数に束縛する。 姓と名を両方とも保存するけど、名だけが必要な場合。 ↓引数authorを取るが、必要なのは名だけで、ちょっとわかりにくい。
(defn greet-author-1 [author]
(println "Hello," (:first-name author)))
必要な部分だけを束縛する。
(defn greet-author-2 [{fname :first-name}]
(println "Hello," fname))
アンダースコアは慣用的に値を気にしない束縛を示すのに使う。
(let [[_ _ z] [1 2 3]] z)
as
でコレクション自体に名前をつけることもできる。
(let [[x y :as coords] [1 2 3 4 5 6]]
(str "x: " x ", y: " y ", total dimensions " (count coords)))
分配束縛を使う。 [w1 w2 w3] への分配束縛によって最初の3つの単語が取り出される。
(require '[clojure.string :as str])
(defn ellipsize [words]
(let [[w1 w2 w3] (str/split words #"\s+")]
(str/join " " [w1 w2 w3 "..."])))
(ellipsize "The quick brown fox jumps over the lazy dogs.")
(resolve 'foo)
(in-ns 'myapp)
(clojure.core/use 'clojure.core)
(def rnd (new java.util.Random))
(. rnd nextInt)
(. rnd nextInt 10)
.
はインスタンスのメソッドだけでなく、クラスメンバへのアクセスにも使える。
(. Math PI)
ブラウザで該当ドキュメントに移動する。
(javadoc java.net.URL)
(defn is-small? [number]
(if (< number 100) "yes" "no"))
(is-small? 50)
(is-small? 101)
分岐後に複数のアクションを起こす、には do
を使う。
do
は副作用をもたらすという明示になる。
(defn is-small? [number]
(if (< number 100)
"yes"
(do
(println "Saw a big number" number)
"no")))
(is-small? 200)
loop
を使っての再帰。
(loop [result [] x 5]
(if (zero? x)
result
(recur (conj result x) (dec x))))
(defn countdown [result x]
(if (zero? x)
result
(recur (conj result x) (dec x))))
(countdown [] 5)
さまざまなカウントダウン。
(into [] (take 5 (iterate dec 5)))
(into [] (drop-last (reverse (range 6))))
(vec (reverse (rest (range 6))))
(defn indexed [coll] (map-indexed vector coll))
(indexed "abcde")
(indexed "abcde")
clojureの for
はループではなく、シーケンスの内包表記である。
(defn index-filter [pred coll]
(when pred
(for [[idx elt] (indexed coll) :when (pred elt)] idx)))
(index-filter #{\a \b} "abcdbbb")
(index-filter #{\a \b} "xyz")
(defn index-of-any [pred coll]
(first (index-filter pred coll)))
(index-of-any #{\z \a} "zzabyycdxx")
(index-of-any #{\b \y} "zzabyycdxx")
(meta #'str)
(defn ^{:tag String} shout [^{:tag String} s] (.toUpperCase s))
(meta #'shout)
あるいは略記法で書ける。
(defn ^String shout [^String s] (.toUpperCase s))
シーケンスはいろんなデータ構造の抽象。
(class (rest [1 2 3]))
(first ["a" "b"])
(rest ["a" "b"])
(cons "a" "b")
マップはキー/値のペアを要素とするシーケンスとして扱える。
(first {:fname "Aaron" :lname "Bedra"})
(rest {:fname "Aaron" :lname "Bedra"})
(cons [:mname "James"] {:fname "Aaron" :lname "Bedra"})
conj
と info
は効率的に追加できる位置に要素を加える。
対象となるデータ構造について一番効率の良い操作をしてくれるので、具体的な実装に結び付けなくても効率的なコードが書ける。
(conj '(1 2 3) :a)
(into '(1 2 3) '(:a :b :c))
(range 10)
(range 10 20)
(range 10 20 2)
↓なぜか実行できない。
(repeat 5 "x")
シーケンスは無限に続くので取り出しが必要。
(take 10 (iterate inc 1))
(defn whole-numbers [] (iterate inc 1))
(take 4 (repeat 1))
(take 10 (cycle (range 3)))
(interpose "," ["apple" "banana" "grapes"])
(use '[clojure.string :only (join)])
(join \, ["apples" "banana", "grapes"])
(take 10 (filter even? (whole-numbers)))
(take 10 (filter odd? (whole-numbers)))
最初の母音に出会うまで文字列から文字を取り出す操作。
(take-while (complement #{\a\e\i\o\u}) "the-quick-brown-fox")
(drop-while (complement #{\a\e\i\o\u}) "the-quick-brown-fox")
(split-at 5 (range 10))
(split-with #(<= % 10) (range 0 20 2))
述語をそれぞれ適応して真偽値を返す。
(every? odd? [1 3 5])
(every? odd? [1 3 8])
いずれかが条件に合わないときはnilを返す。
(some even? [1 2 3])
(some even? [1 3 5])
(some identity [nil false 1 nil 2])
(not-every? even? (whole-numbers))
(not-any? even? (take 10 (whole-numbers)))
各リストに関数を適用する。
(map #(format "<p>%s</p>" %) ["the" "quick" "brown" "fox"])
シーケンスの中身をまとめ上げる。
(reduce + (range 1 11))
(reduce * (range 1 11))
(sort [42 1 7 11])
(sort-by #(.toString %) [42 1 7 11])
ソートの順番を変えられる。
(sort > [42 1 7 11])
(sort < [42 1 7 11])
リスト内包表記バージョン。
(for [word ["the" "quick" "brown" "fox"]]
(format "<p>%s</p>" word))
:when
節を使うと filter
をエミュレートできる。
(take 10 (for [n (whole-numbers) :when (even? n)] n))
1番右側の束縛を最初に繰り返し、1度繰り返すたびにその左側の操作を1つ進める。 なのでrankの繰り返しが先に行われる。
(for [file "ABCDEFGH" rank (range 1 9)] (format "%c%d" file rank))
リストは必要なときに作られる。
(use 'examples.primes)
(def ordinals-and-primes (map vector (iterate inc 1) primes))
(take 5 (drop 1000 ordinals-and-primes))
遅延なので、副作用が動かないように見える。
(def x (for [i (range 1 3)] (do (println i) i)))
x ;; 標準入力に出ない
シーケンスの要素をすべて計算する。
(doall x)
(def x (for [i (range 1 3)] (do (println i) i)))
x
シーケンス関数に渡されると、Javaのコレクションは自動的にシーケンスとして振る舞う。 シーケンスとして振る舞えるコレクションをシーカブルであるという。
配列はシーカブル。
(first (.getBytes "hello"))
(rest (.getBytes "hello"))
(cons (int \h) (.getBytes "ello"))
文字列もシーカブル。
(first "Hello")
(rest "Hello")
(cons \H "ello")
逆にして、戻す。
(reverse "hello")
(apply str (reverse "hello"))
Javaのコレクションを使うのは、JavaのAPIを扱うときだけ(Clojure組み込みのコレクションの方が便利)。
マッチした文字列からなる変更不可なシーケンスを作り出す。 シーケンスライブラリの関数群がただで正規表現にも使えるようになる。
(re-seq #"\w+" "the quick brown fox")
(sort (re-seq #"\w+" "the quick brown fox"))
(drop 2 (re-seq #"\w+" "the quick brown fox"))
(map #(.toUpperCase %) (re-seq #"\w+" "the quick brown fox"))
(import '(java.io File))
(map #(.getName %) (.listFiles (File. "./public/css")))
ディレクトリを深さ優先で辿る。
(count (file-seq (File. "./public")))
最近更新されたファイルだけを調べる。
(defn minutes-to-millis [mins]
(* mins 1000 60))
(defn recently-modified? [file]
(> (.lastModified file)
(- (System/currentTimeMillis) (minutes-to-millis 30))))
(filter recently-modified? (file-seq (File. ".")))
読み込む(readerをオープンしたまま)。
(use '[clojure.java.io :only (reader)])
(take 2 (line-seq (reader "public/index.html")))
リーダーをちゃんと閉じる。
(with-open [rdr (reader "./public/index.html")]
(count (line-seq rdr)))
空行を除いた行数。
(with-open [rdr (reader "./public/index.html")]
(count (filter #(re-find #"\S" %) (line-seq rdr))))
orgコードの行数を数え上げる。
(use '[clojure.java.io :only (reader)])
(defn non-blank? [line] (if (re-find #"\S" line) true false))
(defn non-svn? [file] (not (.contains (.toString file) ".svn")))
(defn org-source? [file] (.endsWith (.toString file) ".org"))
(defn org-loc [base-file]
(reduce
+
(for [file (file-seq base-file)
:when (and (org-source? file) (non-svn? file))]
(with-open [rdr (reader file)]
(count (filter non-blank? (line-seq rdr)))))))
(org-loc (java.io.File. "./"))
シーケンス関数を使うと汎用性の高いコードが書ける。 リスト、ベクタ、マップ、セットに特化した関数もある。
(peek '(1 2 3))
(pop '(1 2 3))
(get [:a :b :c] 1)
(get [:a :b :c] 5) ;; nil
指定のインデックスに新しい値を入れる。
(assoc [0 1 2 3 4] 2 :two)
(subvec [1 2 3 4 5] 3)
(take 2 (drop 3 [1 2 3 4 5]))
特定の構造向けの関数がある場合、それは性能のためであることがほとんど。
(keys {:sundance "spaniel", :darwin "beagle"})
(vals {:sundance "spaniel", :darwin "beagle"})
(get {:sundance "spaniel", :darwin "beagle"} :darwin)
({:sundance "spaniel", :darwin "beagle"} :darwin)
(:darwin {:sundance "spaniel", :darwin "beagle"})
キーとして持っていれば値に関わらずtrueを返す。
(def score {:stu nil :joey 100})
(contains? score :stu)
(def song {:name "Agnus Dei"
:artist "Kryzysztof Penderecki"
:album "Polish Requiem"
:genre "Classical"})
song
(assoc song :kind "MPEG Audio File")
(dissoc song :genre)
(select-keys song [:name :artist])
(merge song {:size 8118166, :time 507245})
同じキーを持っていた場合、どうやって値を組み合わせるかを指定する関数を与える。
(merge-with
concat
{:rubble ["Barney"], :flintstone ["Fred"]}
{:rubble ["Betty"], :flintstone ["Wilma"]}
{:rubble ["Bam-Bam"], :flintstone ["Pebbles"]})
(use 'clojure.set)
(def languages #{"java" "c" "clojure"})
(def beverages #{"java" "chai" "pop"})
与えられたセットの少なくとも片方にある要素を集める。
(union languages beverages)
最初のセットに含まれ、しかし2番めのセットには含まれないような要素を集めたセット。
(difference languages beverages)
言語の名前でもあり、かつ飲み物の名前でもあるようなもの。
(intersection languages beverages)
1文字だけの言語。
(select #(= 1 (.length %)) languages)
関係演算はSQLなどの問い合わせ言語の基礎となっている。
関係演算 | データベース | Clojureの型システム |
関係 | テーブル | セットとして動作するものすべて |
タプル | 行 | マップとして動作するものすべて |
(def compositions
#{{:name "The Art of the Fugue" :composer "J. S. Bach"}
{:name "Musical Offering" :composer "J. S. Bach"}
{:name "Requiem" :composer "Giuseppe Verdi"}
{:name "Requiem" :composer "W. A. Mozart"}})
(def composer
#{{:composer "J. S. Bach" :country "Germany"}
{:composer "W. A. Mozart" :country :Austria}
{:composer "Giuseppe Verdi" :country "Italy"}})
(def nations
#{{:nation "Germany" :language "German"}
{:nation "Austria" :language "German"}
{:nation "Italy" :language "Italian"}})
(rename compositions {:name :title})
SQL SELECT文に該当する抜き出し。
(project compositions [:name])
- 37, 39, 44, 56, 67, 74, 81
clojureでのshell実装。
clojure開発者リッチ・ヒッキーのプレゼン。
リッチ・ヒッキーのプレゼンの解説。 簡単とシンプルさを分ける。抽象化によってシンプルさを保つ。
clojureの本のサンプルコード。
clojureの依存解決ライブラリ。
オブジェクト指向では名詞が重要な地位を占めている。
clojureの特徴がわかる。