関数型プログラミング言語でLisp方言の1つのClojureを触ってみました。(『7つの言語 7つの世界』第7章)

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