ブログトップ 記事一覧 ログイン 無料ブログ開設

翡翠はコンピュータに卵を生むか

2013-10-10

第2章 Clojure ひとめぐり

2.1 フォーム

何がリテラルになるのかについての説明。有理数型が用意されていることなどCLとの重複は多い。

文字列はCLだと文字のベクタだったが、ClojureではJavaの文字列型を使っている。

;; Common Lisp
(vectorp "hoge") ; T
;; Clojure
(vector? "hoge") ; false

BigDecimal(任意精度の浮動小数点)とBigInt(任意精度の整数演算)もJavaの数値型をそのまま使う。リテラル表記はBigDecimalにはM、BigIntにはNを付ける。

(+ 1 (/ 0.00001M 1000000000000000000))
=> 1.00000000000000000000001M

(* 1000N 1000 1000 1000 1000 1000 1000)
=> 1000000000000000000000N

空リストが真。条件部が偽になるのはnilとfalseだけで、残りは全部真になる。

(if '() "True" "False") ; "True"

2.5 Javaを呼び出す

Javaのインスタンスを作るにはnewを使う。

(def rnd (new java.util.Random)) ; => #'user/rnd

メソッド呼出しやフィールドへのアクセスは全部.(ドット)

;; 引数なしのメソッド呼出し
(. rnd nextInt) ; => -1926271163
;; 引数付きのメソッド呼出し (乱数の範囲を指定する)
(. rnd nextInt 10) ; => 9
;; フィールドへのアクセスも似たように書ける
(. Math PI) ; => 3.141592653589793

毎回java.util.Randomと入力するのが面倒なら、useのようにimportを使うことで単にRandomと書ける。

(import '(java.util Random))
(def rnd2 (new Random)) ; => #'user/rnd2

2.6 フロー制御

CLのprognやSchemeのbeginに相当するのは do。CLのdoマクロとは全然違うので注意。

loop-recur構文はSchemeのnamed-letにちょっと似てる。

;; Clojure
(defn my-reverse [lst]
  (loop [lst lst
         product '()]
    (if (empty? lst)
      product
      (recur (rest lst)
             (cons (first lst) product)))))

;; Scheme
(define (my-reverse lst)
  (let loop ((lst lst)
             (product '()))
       (if (null? lst)
         product
         (loop (cdr lst)
           (cons (car lst) product)))))

ClojureのletはCL/Schemeでいうところのlet*と同等。局所変数束縛のところの括弧が少なくなってる。これだとちょっと見にくいのでカンマを入れたりする。Clojureではカンマは空白と同じらしい。じゃあ逆クオートに対するアンクオートはどうなるのかというと~(チルダ)を使う。

;; CL/Scheme
(let* ((i 0)
       (j (+ i 1)))
  (print i)
  (print j))

;; Clojure
(let [i 0 ,
      j (+ i 1)]
  (println i)
  (println j))

condもletと同じように括弧が少ないこと以外は同じ。デフォルト節の条件部に入れるものは真になるものならなんでもいい。今回は:elseとしておく。

(defn fizzbuzz [n]
  (loop [i 1]
    (if (<= i n)
      (do
        (cond (zero? (mod i 15)) (println "Fizz Buzz")
              (zero? (mod i 3)) (println "Fizz")
              (zero? (mod i 5)) (println "Buzz")
              :else (println i))
        (recur (+ i 1))))))

第1章 さあ、始めよう

1章のコードはcode/src/examples/introduction.cljにある。

1.1 なぜ Clojure なのか

Javaとの比較で高階関数を使うことでコードが簡潔に書けることを主張する。マクロの存在も匂わせている。他のLispよりも括弧が少ないというのは視覚上のメリットとS式単位の編集がやりにくくなるというデメリットがあると思う。

1.2 Clojure を書き始めよう

REPLの使い方と関数定義、状態の取扱いについて。セットやリファレンスが何かを説明せずに使いはじめているのは面喰らう構成だ。後でちゃんと説明があるのだろう。

リファレンスというデータ構造があって、その中に値を格納し、参照や変更には専用の関数を必要とする程度に思っておく。

1.3 Clojure ライブラリの探索

CLと同じようにrequireやuseできる。useがCLのuse-packageに相当する。名前衝突してもエラーを出さずに上書きしてしまうらしい。これは微妙に怖い仕様だ。プロジェクトの定義などをすっ飛ばしているけどこれも後でちゃんと説明があるのだろう。

docはCLのdocumentationに相当する。CLの場合は(documentation name-symbol 'function)のように関数の名前空間なのか変数の名前空間なのか指定する必要があったが、ClojureはLisp-1なので(doc name-symbol)とするだけでいい。(find-doc "string")や(apropos "string")を使って関数を探す感じか。

プログラミングClojure第二版を買ったので環境づくり

オーム社のページからプログラミングClojure第二版(電子版)を買ってみた。

第二版からはプロジェクト管理ツールleiningenを使うようになったのでこれをインストールする必要がある。とはいえシェルスクリプトに実行権限を与えて実行パスの通ったディレクトリに置くだけである。

サンプルコードにはproject.cljが入っていて、ここに依存するパッケージやClojureのバージョン(1.3.0)が指定してある。依存ライブラリのバージョンまで指定されているので、確実にサンプルコードを動かすことができる。第一版でサンプルコードがまともに動かずに苦しんだ自分にはありがたい。

サンプルコードのルートディレクトリまで移動して

$ lein deps

と入れるだけで必要なファイルがすべて自動的にダウンロードされる。

次に編集環境を作る必要がある。Emacsは入ってるとして、clojure-mode.elnrepl.elは自分で入れる必要がある。最近はEmacsにパッケージマネージャpackage.elが標準で付いてくるのでここからインストールする方法もある。.emacsに以下のコードを追加して、

;; Marmaladeの為の設定
(require 'package)
(add-to-list 'package-archives 
    '("marmalade" .
      "http://marmalade-repo.org/packages/"))
(package-initialize)

M-x package-refresh-contents [RET] をやることでレポジトリが更新される。その後に M-x package-install とやって、パッケージ名を入力すると、関連するファイルがダウンロード、バイトコンパイルされ、~/.emacs.d/elpa/ にインストールされる。

パッケージ名としてnrepl.el、clojure-modeと入れれば依存ライブラリも含めてまとめてインストールされる。


自分はgit cloneでソースをダウンロードして.emacsからrequireするようにした。nrepl.elはSLIMEほどきめ細かではないにせよ、leiningenとの連携を考えて作ってあるらしい。

Emacsでサンプルコード中のproject.cljを開いて、 M-x nrepl-jack-in と入れるとREPLが起動する。念のためバージョンを確認するとちゃんと1.3.0になっている。

user => (clojure-version)
"1.3.0"

これで環境構築はできた。

2011-09-27

クラスパスの指定

あんまりJavaとか使ったことなかったので、Clojureでクラスパスを指定するところで詰まってしまった。Clojureの公式サイトでは、起動コマンドは

$ java -cp clojure.jar clojure.main

とのことなので、cpオプションのところに使いたいライブラリのパスを指定していけばいいらしい。例えば、clojure-contribとプログラミングClojureのサンプルコード(https://github.com/stuarthalloway/programming-clojure)を読み込むには以下のような起動スクリプトを書く。

#!/bin/sh

CLOJURE_DIR=$HOME/.clojure
CLOJURE_JAR=$CLOJURE_DIR/clojure.jar
CONTRIB_JAR=$CLOJURE_DIR/clojure-contrib.jar
PROGRAMMING_CLOJURE_DIR=$CLOJURE_DIR/programming-clojure

CLASSPATH=$CLASSPATH:$CLOJURE_JAR:$CONTRIB_JAR:$PROGRAMMING_CLOJURE_DIR

java -cp $CLASSPATH clojure.main

Clojureを起動して次のコードを打ち込むことで確認できる。

user=> (use 'clojure.contrib.classpath)
nil
user=> (classpath)
(#<File > #<File /home/masatoi/.clojure/clojure.jar> #<File /home/masatoi/.clojure/clojure-contrib.jar> #<File /home/masatoi/.clojure/programming-clojure>)

swank-clojureでクラスパスを指定する方法はSLIMEでClojureを使うに追記しました。

2011-02-11

SLIMEでClojureを使う

Clojureのバージョンアップのせいか、どうも前に他のサイトで見た方法がうまくいかなくなってしまった。

試行錯誤してみた結果とりあえずちゃんと動くようになったので、色々とアヤシイところはあるもののまとめてみる。

SLIMEのインストール

Emacs Lispは~/elisp以下にインストールするものとする。まずはGitを利用して最新版をダウンロードする。87MBもあるのでけっこう時間かかる。SBCLは既に入っているものとする。

$ cd ~/elisp
$ git clone git://git.boinkor.net/slime.git

~/.emacsに以下のように書く

;; 処理系の指定
(setq inferior-lisp-program "sbcl")
;; SLIMEへのロードパスを通す
(add-to-list 'load-path "~/elisp/slime/")
(add-to-list 'load-path "~/elisp/slime/contrib/")
(require 'slime)
(slime-setup '(slime-repl)) ; slime-scratch slime-fancy slime-asdf
(add-hook 'slime-mode-hook
	  (lambda ()
	    ;; outline-minor-modeを有効化
	    (outline-minor-mode t)
	    ;; 各種キーバインド
	    (define-key slime-mode-map "\C-c\C-i" 'comment-region)
	    (define-key slime-mode-map "\C-c\C-o" 'uncomment-region)
	    (define-key slime-mode-map "\C-c\C-c" 'slime-hyperspec-lookup)
	    (define-key slime-mode-map "\M-n" 'scroll-up-one-line)
	    (define-key slime-mode-map "\M-p" 'scroll-down-one-line)
	    ))
(setq slime-net-coding-system 'utf-8-unix) ; 文字コードの指定
(setq slime-startup-animation nil)
(setq slime-truncate-lines nil)

再インストールの際などはコンパイラのバージョンが違っていたりするので、以前のコンパイル済みファイルは消しておいた方がいいかも。~/.slimeにあるので rm -r ~/.slime などとしておく。

Emacsを起動して M-x slime で関連ファイルのコンパイルが始まり、SLIMEのプロンプトが出てきたら成功。

Clojureのインストール

公式のダウンロードページから安定版のclojure-1.2.0とclojure-contrib-1.2.0をダウンロードしてくる。

clojure-contribの方はjarファイルが入っていないのでビルドする必要がある。ビルドのためのソフトがantからMavenっていうのに変わったらしい。/usr/ports/devel/maven2からMaven2を入れて、READMEを読みつつclojure-contribをビルド。

できたjarファイルは ~/.clojure というディレクトリを作ってそこに置いておく。

clojure-modeのインストール

$ cd ~/elisp
$ git clone git://github.com/jochu/clojure-mode.git 

~/.emacsに以下のように書く

(add-to-list 'load-path "~/elisp/clojure-mode")
(require 'clojure-mode)
(autoload 'clojure-mode "clojure-mode" "A major mode for Clojure" t)

Leiningenのインストール

id:t2ruさんの記事参照。基本的にはシェルスクリプトを落としてきて実行権限つけてパスの通ったところに置くだけ。

swank-clojureのインストール

$ cd ~/elisp
$ git clone git://github.com/jochu/clojure-mode.git

ソースをダウンロードしたはいいが、swank-clojureのビルドがよく分からないのでLeiningenでインストールしてjarファイルを~/.clojureにコピーする。

$ lein install swank-clojure 1.3.0-SNAPSHOT
$ cp ~/.m2/repository/swank-clojure/swank-clojure/1.3.0-SNAPSHOT/swank-clojure-1.3.0-SNAPSHOT.jar ~/.clojure/swank-clojure.jar

~/.emacsに以下のように書く

(add-to-list 'load-path "~/elisp/swank-clojure")
(require 'swank-clojure)
(require 'assoc)
;; /.clojure 内の各jarファイルにクラスパスを通す
(setq swank-clojure-jar-home "~/.clojure")
;; slime-lisp-implementationsにclojureの呼び出しコマンドを追加する
(swank-clojure-reset-implementation)

最後に各処理系別にコマンドを用意。

(add-to-list 'slime-lisp-implementations '(sbcl ("sbcl")))
(defun slime-clojure () (interactive) (slime 'clojure))
(defun slime-sbcl () (interactive) (slime 'sbcl))

M-x slime-clojureで起動を確認して終了。これでSBCLとClojureの使い分けができるようになった。

追記

上のように設定してClojure+SLIMEの起動はうまくいったわけだが、デバッガから抜けるときに"error in process filter: Wrong number of arguments: nil,0"というエラーメッセージがミニバッファに毎回出るようになってしまった。しかもその度に数秒止まるのでなんとかしたい。https://github.com/technomancy/swank-clojure/issues/issue/31 によると、SLIMEがELPAからインストールしたものでないとこれが出るらしい。

git cloneで取ってきた最新版でやるときは、多少強引だが、SLIMEの展開先にあるslime.elの中のslime-dispatch-eventの定義を以下のように変更してしまう。

(defun slime-dispatch-event (event &optional process)
  (let ((slime-dispatching-connection (or process (slime-connection))))
    (or (run-hook-with-args-until-success 'slime-event-hooks event)
        (destructure-case event
          ((:emacs-rex form package thread continuation)
           (when (and (slime-use-sigint-for-interrupt) (slime-busy-p))
             (slime-display-oneliner "; pipelined request... %S" form))
           (let ((id (incf (slime-continuation-counter))))
             (slime-send `(:emacs-rex ,form ,package ,thread ,id))
             (push (cons id continuation) (slime-rex-continuations))
             (slime-recompute-modelines)))
          ((:return value id)
           (let ((rec (assq id (slime-rex-continuations))))
             (cond (rec (setf (slime-rex-continuations)
                              (remove rec (slime-rex-continuations)))
                        (slime-recompute-modelines)
-			(funcall (cdr rec) value))
+			(ignore-errors (funcall (cdr rec) value))) ; ここでエラーが起きているのでignore-errorsで黙らせる
                   (t
                    (error "Unexpected reply: %S %S" id value)))))
          ((:debug-activate thread level &optional select)
           (assert thread)
           (sldb-activate thread level select))

いいのかなぁ。これ・・・ しかしとりあえずはちゃんと動くようになったのよしとする。

追記2 クラスパスの指定

変数swank-clojure-classpathにクラスパスがリストとして入ってるので、追加したいパスをpushする。

例えば、プログラミングClojureのサンプルコードを利用したいなら、

$ cd ~/.clojure
$ git clone git://github.com/stuarthalloway/programming-clojure.git

でソースコードを取得して、.emacsに以下を追加する。

(push "~/.clojure/programming-clojure" swank-clojure-classpath)