『7つの言語 7つの世界』(オーム社)の第7章で紹介されているClojureです。
Clojure
http://clojure.org/
1. Clojureについて
Clojureは関数型言語Lisp方言の1つです。Lipsは非常に歴史が古い言語であるため、ClojureのほかにEmacs Lisp、Common Lisp、Schemeなど多くの方言をもっています。Lispがもつ高い柔軟性とJavaとの統合性の高さから(あと括弧が他の方言と比べると少ないらしい)、多々あるLisp方言のなかでもっとも成功しているとされるClojureを『7つの言語 7つの世界』は選択しています。
ここではClojureの紹介をしていくつもりですが、私にとって初めてさわる「Lisp」であり、Lispとしての特徴とClojureとしての特徴の区別がほとんどつかないため、Lispの特徴とClojureの特徴が混在した紹介にならざるを得ませんでした。ご了承ください。
なお、他の言語もそうですが、Clojureでは 『7つの言語 7つの世界』だけではなく、Web上の掲載されている様々な情報を参考にさせていただきました。
2. Clojureのインストール
Web上にはいろいろなインストール方法が紹介されていましたが、MacOSX Lionへのインストールの場合、HomeBrewでLeiningenをインストールすれば必要なものがもろもろインストールされて一発でした。
$ brew install leiningen
3. Hello,World
起動と終了は以下の通り。終了方法は最初にそれを知った時に「なるほど、そうか!」という気分になりますね。
・起動
$ lein repl REPL started; server listening on localhost port ○○○○○ user=>
・終了
user=> (exit) $
文字列はダブルクォートで囲います。
user=> (println "Hello,World") Hello,World nil
制御構文はこんな感じ。
user=> (if (> 2 1) (println "2")) 2 nil
演算
user=> (+ 1 1) 2 user=> (- 2 1) 1 user=> (+ 1 2 3) 6 user=> (* (+ 1 1)(- 5 1)) 8
上はそれぞれ
- (+ 1 1) → 1+1
- (- 2 1) → 2-1
- (+ 1 2 3) → 1+2+3
- (* (+ 1 1)(- 5 1)) → (1+1)*(5-1)
をあらわしています。
4. リスト
Lispではリストは以下のように丸括弧( )で括弧って要素を空白で区切って記述します。
(要素 要素 要素 ・・・)
つまり・・
(1 2 3)
というデータセットも
(println “2”)
というコードも同じ記述方法で記述します。Lispはあらゆるものがリストだと言われ、データ以外のコードも必要に応じてリストとして処理することができます。これがLispが強力なメタプログラミング機能を持つ理由の1つであるようです。
コードの記述はリストの最初の要素として関数や演算子などを記述する前置記法と呼ばれる記述方法で記述します。残りの要素は引数です。
(関数 引数 引数・・)
ex. (println “2”)
(演算子 引数 引数・・・)
ex. (+ 1 1)
データのセットとしてリストを記述したい場合に
(1 2 3)
とそのまま記述してしまうと最初の要素が関数と評価されてエラーになってしまいます。
list関数を使用するか、括弧の前にアポストロフィ(‘)を置きます。
user=> (list 1 2 3) (1 2 3) user=> '(1 2 3) (1 2 3)
ちなみに式とコードと混同しやすいため、Clojureでは慣習的に式はリストで、データのセットは後述するペクタを使用するそうです。
5. ベクタ・セット・マップ
ペクタ(vector。「ベクトル」と呼ぶ人も)という角括弧[ ]で記述するデータセットです。また、角括弧を使用せずとも、リストでvector関数を使用してもベクタを生成できます。
user=> [1,2,3] [1 2 3] user=> (vector 1 2 3) [1 2 3]
慣習的な使い分けはともかく、その機能を見る限り、データセットとして使用する場合のリストとベクタの違いがよくわからなかったのですが、内部構造でのデータの持ち方が異なるようです。
リストとベクタは要素の順序が重要な意味を持ちますが、意味を持たないセットというデータセットもあります。
user=> #{1 2 3} #{1 2 3}
キーと値を対応づけるRubyでいうハッシュに相当するデータセットがマップです。上のセットというデータセットと同じ{ }で囲いますが、{キー 値 キー 値}という順序で並んでいます。キーはコロンをつけたシンボルが使用されます。
user=> {:a "a" :b "b"} {:a "a", :b "b"} user=> {:a 1 :b 1} {:a 1, :b 1}
読みづらいということで、Clojureではキーと値ごとに区切り記号としてカンマをいれることも可能です。
user=> {:a "a", :b "b"} {:a "a", :b "b"} user=> {:a 1, :b 1} {:a 1, :b 1}
6.シーケンス
シーケンスとは・・・
とかけるほどシーケンスのことをよく理解していませんが、リスト、ベクタ、セット、マップ、文字列、ファイルシステム構造をラップして共通のインターフェイスを提供する抽象化されたなにか(型?)のようです。共通の関数が使える、ということですかね。
user=> (count (list 1 2 3)) 3 user=> (count [1 2 3]) 3 user=> (count #{1 2 3}) 3 user=> (count "abc") 3
7.変数
Clojureでの変数の定義以下を読むとdef関数を使用するようです。
・Clojure – vars
user=> (def a 1) #'user/a user=> a 1
一度変数を定義しても変更可能な(mutable)ようです。
user=> (def a 2) #'user/a user=> a 2
ソフトウェアトランザクショナルメモリを使用して同じ変更可能な場所をより安全に参照するためにデータをラップするref関数というものもあるようです。
user=> (ref "Totoro") #<ref@1726c5a5: "Totoro">
ref関数でラップした値をdef関数で変数とひもづけることで参照することができるようになります。
user=> (def movie (ref "Totoro")) #'user/movie user=> @movie "Totoro" user=> (deref movie) "Totoro"
参照するときは変数の頭に@をつけるか、deref関数を使用します。
参考: Clojure – refs
ref関数でラップしたデータは同じトランザクションのスコープ内でしか変更することができませんが、そのスコープ外でデータをラップするためにアトムといものがClojureにはあります。
user=> (atom "Totoro") #<atom@762d80ae: "Totoro"> user=> (def movie (atom "Totoro")) #'user/movie user=> movie #<atom@54b4b0a4: "Totoro"> user=> @movie "Totoro"
8.関数
defn関数で関数を定義することができます
user=> (defn hello [x] (str "Hello," x)) #'user/hello user=> (hello "world") "Hello,world"
defn関数は引数をうけないとエラーを検出していまうようですが、引数を受け取らない関数を定義する場合は、fn関数では無名関数をつくり、それをdef関数で変数に代入?する方法があるようです。
user=> (def hello_world (fn [] (println "Hello,World"))) #'user/hello_world user=> (hello_world) Hello,World nil
参考
・プログラミングClojure 勉強メモ 第1章 Getting Started – Life is a Role Playing Game
・関数 – Closer to Clojure
・untitled: 『プログラミング Clojure』 で勉強 第2章 – 2