7/19/2011

第6回 Common Lispライブラリを書く

 僕のブログで「Modern Common Lispはターゲットをどこに置いているのか」という質問をいただきました。最初は環境構築から始まっており、初心者向けに見えますが、次はHello, Worldもせずにライブラリの解説をしています。

 Modern Common Lispのターゲットは、これからのCommon Lispを学びたい人です。当初はこの連載を読めばCommon Lispについてのすべてを学べるAll-In-One形式にしようと考えていたのですが、それでは時間がかかりすぎてしまうため、いくつかの過程を省いて進めます。たとえば、Lispでは必要なリスト処理などはここでは解説しません。理由は、それを学ぶための書籍が既に多く存在するからです。Common Lispの基本文法はそれらの書籍を参照してください。この連載は、Common Lispの入門書と並行、または読了後に読むことをおすすめします。

 さて、前回の僕の記事では主要なライブラリとその使い方について紹介しました。今回は自分のCommon Lispのライブラリを書く手順についてです。

Sleep Sortのライブラリをつくろう

 1ヶ月以上前のことになりますが、Sleep Sortというソートアルゴリズムが話題になりました。

 実装が簡単なので、例として今回はこのライブラリを作りたいと思います。

CL-Projectでスケルトン生成

 まずはライブラリの雛形を作ります。雛形の生成にはCL-Projectというライブラリを使います。まずはQuicklispでインストールしましょう。

(ql:quickload :cl-project)

 雛形を作るには関数make-projectを使います。あらかじめ依存することが分かっているライブラリはこのタイミングで指定しておきましょう。今回はcl-annot(後述)と、スレッドを使いそうなのでbordeaux-threadsの2つを指定しています。

(cl-project:make-project #p"lib/sleepsort/"
  :author "Eitarow Fukamachi"
  :email "e.arrows@gmail.com"
  :license "LLGPL"
  :depends-on '(:bordeaux-threads cl-annot))
;-> writing /Users/fukamachi/Programs/lib/sleepsort/.gitignore
    writing /Users/fukamachi/Programs/lib/sleepsort/README.markdown
    writing /Users/fukamachi/Programs/lib/sleepsort/sleepsort-test.asd
    writing /Users/fukamachi/Programs/lib/sleepsort/sleepsort.asd
    writing /Users/fukamachi/Programs/lib/sleepsort/src/sleepsort.lisp
    writing /Users/fukamachi/Programs/lib/sleepsort/t/sleepsort.lisp
;=> #P"/Users/fukamachi/Programs/lib/sleepsort/sleepsort.asd"

 これで#p"lib/sleepsort/"にスケルトンができました。空のプロジェクトはそのままの状態でもql:quickloadすることができます。

(ql:quickload :sleepsort)
;-> To load "sleepsort":
      Load 1 ASDF system:
        sleepsort
    ; Loading "sleepsort"
    [package sleepsort]
;=> (:SLEEPSORT)

※注) もし#p"lib/sleepsort/"にパスが通っていない場合、REPLを再起動するとロードできなくなります。ローカルのプロジェクトをQuicklispでロードできるようにするには第2回 Quicklispによるライブラリ環境を参照してください。

雛形の構成

 CL-Projectが生成する雛形は以下のような最小構成です。

  • README.markdown: README。
  • sleepsort.asd: ライブラリ(system)の定義ファイル。
  • sleepsort-test.asd: ライブラリのテスト用の定義ファイル。
  • src/
    • sleepsort.lisp: メインのソースファイル。
  • t/
    • sleepsort.lisp: メインのテストファイル。

 ディレクトリトップには.asdファイルが2つあります。これは他の言語で言うところのMakefileです。ASDFによるライブラリの定義ファイルです。Quicklispはこのファイルから必要なファイルを判断してライブラリをロードしています。

 srcディレクトリは主なソースコードを置く場所です。この中には最初1つのファイルが置かれていますが、他にファイルを分けたいと思ったときにはこのディレクトリにファイルをどんどん追加していきます。

 tディレクトリは自動テストを置く場所です。

関数を実装

 では、肝心の関数の定義をします。src/sleepsort.lispを開いてください。以下のようになっているはずです。

#|
  This file is a part of sleepsort project.
  Copyright (c) 2011 Eitarow Fukamachi (e.arrows@gmail.com)
|#

(in-package :cl-user)
(defpackage sleepsort
  (:use :cl))
(in-package :sleepsort)

;; blah blah blah.

 これにsleepsortという関数を追加します。

#|
  This file is a part of sleepsort project.
  Copyright (c) 2011 Eitarow Fukamachi (e.arrows@gmail.com)
|#

(in-package :cl-user)
(defpackage sleepsort
  (:use :cl)
  (:import-from :bordeaux-threads
                :make-thread
                :join-thread))
(in-package :sleepsort)

(cl-annot:enable-annot-syntax)

@export
(defun sleepsort (&rest args)
  "A function to do 'sleep sort' arguments."
  (let* (result
         (threads (mapcar (lambda (arg)
                            (bordeaux-threads:make-thread
                             (lambda ()
                               (sleep arg)
                               (push arg result))))
                          args)))

    (dolist (thread threads)
      (bordeaux-threads:join-thread thread))

    (nreverse result)))

 @exportアノテーションを有効にするためにcl-annotを使っています。このアノテーションを使うと、指定した関数がパッケージ外でも参照できるようになります。

 cl-annotの詳しい解説は、作者の松山が今後紹介記事を書いてくれる予定です。

テストを書く

 モダンなライブラリに自動テストは欠かせません。雛形にはテスト用のファイルも含まれています。t/sleepsort.lispを開いてください。

#|
  This file is a part of sleepsort project.
  Copyright (c) 2011 Eitarow Fukamachi (e.arrows@gmail.com)
|#

(in-package :cl-user)
(defpackage sleepsort-test
  (:use :cl
        :sleepsort
        :cl-test-more))
(in-package :sleepsort-test)

(plan nil)

;; blah blah blah.

(finalize)

 この"blah blah blah."の部分を消して、以下のように置き換えてみます。

(diag "Testing 'sleepsort'. Wait some seconds...")
(is (sleepsort 5 3 6 3 6 3 1 4 7) '(1 3 3 3 4 5 6 6 7))

 テストを走らせるには(test-system :sleepsort)を実行します。

(test-system :sleepsort)
To load "sleepsort-test":
  Load 1 ASDF system:
    sleepsort-test
; Loading "sleepsort-test"
[package sleepsort-test]
# Testing 'sleepsort'. Wait some seconds...
ok 1

(:SLEEPSORT-TEST)

 Sleep Sortの実行は、その性質上時間がかかります。7秒ほど待ったあと、"ok"と表示されればテスト成功です。

 詳しいCL-TEST-MOREの説明はhttps://github.com/fukamachi/cl-test-moreを参照してください。

Quicklispに登録する

 せっかく作った有用なライブラリはQuicklispに登録すべきです。現在Quicklispへの登録は承認制になっており、月に一度の更新時に取り込まれます。日にちは固定ではないですが、だいたい毎月20日前後に更新されます。

 登録の申請はGitHub Issuesで行います。

 右上の「New Issue」をクリックし、ライブラリ名とリポジトリのURLを書いて登録してください。

おわりに

 さて、今回は自分のライブラリを作って公開するまでを紹介しました。公開するためでなくても、何かアプリケーションを作るときはasdファイルを書いてモジュール単位で管理するため、Common Lispでまとまったプログラムを書くときには必要な手順でしょう。

 次回の僕の回ではCL-TEST-MOREでの自動テストについてより詳しく解説します。

7/10/2011

第5回 SLIMEの使い方 開発サイクルについて

前々回の「基礎編」 ではSLIMEのインストール方法とその基本的な使い方について説明しましたが、 具体的にどのように開発したら良いか分からないと思います。今回はその補足 としてSLIMEにおける開発サイクルについて説明します。

SLIMEにおける開発サイクル

SLIMEにおける開発サイクルは基本的には次のようになります。

  1. M-x slimeでSLIMEを起動
  2. lispファイルを編集
  3. REPLバッファで動作確認
  4. 2に戻る

一般的な開発サイクルには「コンパイル」とか「アプリケーションの再起動」 というタスクがありますが、SLIMEの開発サイクルにはそれがありません。その 代わりSLIMEには、アプリケーションへの変更を即座に反映させる仕組みがあり ます。これにより、待ち時間を極力短縮して、またエディタとターミナルを行 き来することなく、効率的に(また気持ち良く)開発することが可能になりま す。この、いわゆるインクリメンタルなアプリケーションの構築は、SLIMEを使 う最大の利点と言っても過言ではありません。

もっとも、(2)の「lispファイルを編集」するための十分な知識を持っていなけ れば、その利点もあまり感じられないでしょう。本連載でもこの点を重点的に 解説していく予定です。本エントリの後半もその解説になります。

Webアプリケーションなどを開発する際は、(3)は「ブラウザで動作確認」にな ります。もちろんユーティリティ関数のテストやモデルデータの確認をREPLで 行うこともあります。要は使い分けです。

ところで、アプリケーションをインクリメンタルに構築していくという性質上、 何かの拍子に変数の値がおかしくなったり、謎のエラー(というわけではない ですが、原因を考えるのが本質的でないエラー)が発生したりします。そうい う時は、M-x slime-restart-inferior-lispとして開発サイクルを一度リセッ トします。この操作は重要なので是非覚えてください。

バッファでの操作

ここでの操作は上記した開発サイクルの(2)に該当します。SLIMEで一番重要な 操作ですので、是非習得してください。

C-c C-c (slime-compile-defun)

このコマンドは現在ポイントしているトップレベル関数(実は関数以外でも有 効)をコンパイルするコマンドです。コンパイルというと少しヘビーなイメー ジを持ちますが、ここでは関数を定義するぐらいのイメージで問題ありません。 実際に例を示します。まず適当なlispファイルに次のコードを書いてくださ い。

(defun fact (n)
  (if (<= n 1)
      0
      (* n (fact (1- n)))))

次にこのフォームをポイントしてC-c C-cします。コンパイルが成功したとい うメッセージがミニバッファに表示されるはずです。これでfact関数が定義 され利用できるようになりました。

C-c C-zあるいはM-x slime-replとしてREPLバッファを表示し、次のコード を実行してみましょう。

CL-USER> (fact 10)
0

結果が0になっていて、どうもfact関数にバグがあるようです。実はnが1以 下のときに1ではなく0を返してしまっていさう。そこで再びlispファイル を開いて次のように修正してください。

(defun fact (n)
  (if (<= n 1)
      1
      (* n (fact (1- n)))))

そして、先と同じようにC-c C-cします。関数が定義できたらC-c C-zある いはM-x slime-replでREPLバッファを表示し、先と同じコードを実行してみ ましょう。

CL-USER> (fact 10)
3628800

正しい結果が返ってきていますね。この例でも分かるように、バッファでの操 作で一番重要なコマンドはC-c C-cです。関数定義(もっと言えばトップレベ ルフォーム)を編集した際は、C-c C-cする癖を付けるのが良いでしょう。

また、C-c C-cすることで、コンパイラがおかしなコードを拒否したり警告し てくれます。これはバグの早期発見に有効です。試しに次のようなコードを書 いてC-c C-cしてみてください。

(defun f (n)
  (let ((m (* n 2)))
    (1+ n)))

するとlet部分に下線が引かれると思います。この下線は警告を表わしており、 C-x \``あるいはM-x next-error`で警告の内容を詳細表示できます。

cd /home/tomo/tmp/
1 compiler notes:

/home/tomo/tmp/a.lisp:2:3:
  style-warning: Unused lexical variable M

このようにmという変数が未使用であるのが分かりますね。

余力がある方は、明らかにエラーとなるコードを書いてみて、C-c C-cしてみ ることをおすすめします。

C-c C-k (slime-compile-and-load-file)

C-c C-cほどではありませんが、それでも頻繁に使うのがC-c C-kです。こ のコマンドはファイル全体をコンパイルしてロードします。「コンパイル」と か「ロード」はCommon Lispの用語ですが、ここではあまり気にせず、ファイル を先頭から読み直すコマンド、ぐらいのイメージで捉えてください。

C-c C-kは基本的には、まだロードされていないファイルをロードするときに 使います。このとき、C-c C-c同様、コンパイルできないコードはエラーや警 告として視覚的に表示され、C-x \``あるいはM-x next-error`で順番に確認 することができます。

またC-c C-kは、C-c C-cし忘れていないことを確定するのにも便利です。 その他にも対応していない括弧などを検出するのにも使えます。

一般的に、何かおかしくなったらC-c C-kしてみる、それでも駄目なら上記し たM-x slime-restart-inferior-lispする、という運用になります。

C-c C-z (slime-repl)

すでに登場しましたが、このコマンドはREPLバッファを表示します。REPLバッ ファは頻繁に使うことになるので、是非覚えてください。

M-. (slime-edit-definition)

このコマンドは現在ポイントしているシンボルの定義を探すコマンドです。コー ディング時はもちろんのことながら、コードリーディング時やデバッグ時にも 非常に有用です。次に説明するM-,と対で覚えてください。

M-, (slime-pop-find-definition-stack)

このコマンドはM-.を実行したときの場所に戻るコマンドです。M-.で定義 を確認した後に、M-,で元の場所に戻るというのが一般的な使い方です。

REPLでの操作

「バッファでの操作」で説明した操作はREPLでも有効ですが、いくつか追加機 能が用意されているので、それを紹介します。

M-p, M-n

M-p, M-nはそれぞれ前の履歴、次の履歴を補完します。最低限これだけは 記憶してください。同じ式を何度も入力するのはよしましょう。

M-r

このコマンドは正規表現で履歴を検索して補完します。目的の履歴が最初に補 完されない場合は、M-p, M-nで順番に探します。インクリメンタルな検索 が出来れば便利ですが、現状ではそのような機能はないようです。誰か anythingで実装してくれないかな。

特殊な変数

REPL(Top Level Loop)では*, **, ***という特殊な変数が利用できま す。*には前回評価した式の結果が入り、**には前々回、***には前々々 回に評価した式の結果が入ります。評価結果を再利用するときに便利です。簡 単な例を示しておきます。

CL-USER> (+ 1 2)
3
CL-USER> (* * *)
9
CL-USER> (- * **)
6

二番目の式の最初の*は変数参照じゃなくて関数適用です。誤解しないように。 よく分からない人はCommon Lispの入門ページなどで勉強してください。

まとめ

とりあえず今回はここまでです。次回以降は実際に何か作りながら、より実践的な 内容について解説したいと思います。

4/23/2011

第4回 Common Lispライブラリガイド

 さて、Modern Common Lispはこれで4回目です。環境構築も完成に近づき、Common Lispでプログラムを始められる状態になりつつあります。ブログの主題も環境構築から実践へと移ります。

 今回はCommon Lispでプログラムを書く際によく必要になるであろうライブラリの紹介です。Common LispはSchemeと比べると仕様の大きな言語には違いありませんが、最近普及しているPythonなどに比べると標準ライブラリも小さいです。そのため、適切なライブラリを適切に使用するという能力は、他の言語以上にCommon Lispで必要になるでしょう。

 ここで紹介するライブラリはすべてQuicklispに入っているのですぐ利用できます。まだインストールしていない人は以下のエントリを参考にインストールしてください。

正規表現を使う - CL-PPCRE

 Common LispにはPerlのように標準で正規表現が付属するわけではありませんが、Perl5互換の正規表現ライブラリ「CL-PPCRE」があります。

 Let Over Lambdaでべた褒めされているのが印象的でした。ガリガリにチューニングされていてPerlの正規表現エンジン(つまりCで書かれた正規表現エンジン)よりも高速らしいです。

 以下はUserAgentを元にマッチングするサンプルコードです。

(defvar user-agent "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_6) AppleWebKit/534.28 (KHTML, like Gecko) Chrome/12.0.728.0 Safari/534.28")

 Common LispでUserAgentによるアクセス判定をしてみます。

(import '(ppcre:scan ppcre:scan-to-strings ppcre:regex-replace-all ppcre:split))

;; 通常のマッチング
;; Macからのアクセスか判定
(scan "\\(Macintosh;" user-agent)
;=> 12
;   23
;   #()
;   #()

;; マッチした部分を取り出したい
;; クライアントのMacのバージョンを知りたい
(scan-to-strings "Mac OS X ([^\\)]+)" user-agent)
;=> "Mac OS X 10_6_6"
;   #("10_6_6")

;; 文字列置換
;; Chromeの部分をMSIEに変える
(regex-replace-all "Chrome" user-agent "MSIE")
;=> "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_6) AppleWebKit/534.28 (KHTML, like Gecko) MSIE/12.0.728.0 Safari/534.28"
;   T

;; 文字列分割
;; クエリを分解
(split "&" "name=Eitarow%20Fukamachi&age=23")
;=> ("name=Eitarow%20Fukamachi" "age=23")

 Let Over Lambdaではリードマクロを使って関数適用のように正規表現を扱う方法が紹介されています。より簡単な表記をしたい人には参考になるでしょう。

日付を処理したい - LOCAL-TIME

 日付操作はお遊びプログラムなどで出てくる定番の一つです。年末になるとよくカウントダウンとかして、新年まであと何秒とかやりますね。Common Lispで日付を扱うには「LOCAL-TIME」を使います。

 簡単なサンプル。

(import '(local-time:now local-time:parse-timestring))

;; 現在時刻 (返り値はTIMESTAMPオブジェクト)
(now)
;=> @2011-04-11T18:57:23.493142+09:00

;; 文字列からのparse
(parse-timestring "2015-02-18")
;=> @2015-02-18T09:00:00.000000+09:00

 ちなみに2015/02/18は僕が生まれて10000日目らしいです(日齢計算より)。実際に試してみます。

(import '(local-time:timestamp-difference local-time:parse-timestring))

(/ (local-time:timestamp-difference
    (local-time:parse-timestring "2015-02-18")
    (local-time:parse-timestring "1987-10-03"))
   (* 24 60 60))
;=> 10000

 逆に自分の生誕10000日目を知るには以下の関数に自分の誕生日を渡せばよいです。

(import '(local-time:timestamp+ local-time:parse-timestring))

(defun your-10000th-day (daystring)
  (timestamp+
    (local-time:parse-timestring daystring)
    864000000
    :sec))

シェルコマンドを実行したい - trivial-shell

 Lispコードからシェルコマンドを実行するには「trivial-shell」が使えます。

;; シェルコマンドを実行
(trivial-shell:shell-command "ls ~/Programs/lib/clack/")
;=> (#P"/Users/fukamachi/Programs/lib/clack/.git/"
;    #P"/Users/fukamachi/Programs/lib/clack/.gitignore"
;    #P"/Users/fukamachi/Programs/lib/clack/README.markdown"
;    #P"/Users/fukamachi/Programs/lib/clack/clack-test.asd"
;    #P"/Users/fukamachi/Programs/lib/clack/clack.asd"
;    #P"/Users/fukamachi/Programs/lib/clack/src/"
;    #P"/Users/fukamachi/Programs/lib/clack/t/"
;    #P"/Users/fukamachi/Programs/lib/clack/tmp/")

;; シェルをZshに変更 (デフォルトは "/bin/sh")
(setf trivial-shell:*bourne-compatible-shell* "/bin/zsh")
;=> "/bin/zsh"

;; 環境変数の値を取得
(trivial-shell:get-env-var "PATH")
;=> "/usr/bin:/bin:/usr/sbin:/sbin"

ファイル操作をしたい - CL-FAD

 日常の簡単なスクリプトなどを書くとき、Lispからファイルシステムにアクセスすることもあるでしょう。「CL-FAD」を使えばOSに依存しない可搬なプログラムが書けます。

(import '(cl-fad:list-directory cl-fad:walk-directory cl-fad:delete-directory-and-files))

;; ディレクトリのファイル一覧
(list-directory #p"~/Programs/lib/clack/")
;=> (#P"/Users/fukamachi/Programs/lib/clack/.git/"
;    #P"/Users/fukamachi/Programs/lib/clack/.gitignore"
;    #P"/Users/fukamachi/Programs/lib/clack/README.markdown"
;    #P"/Users/fukamachi/Programs/lib/clack/clack-test.asd"
;    #P"/Users/fukamachi/Programs/lib/clack/clack.asd"
;    #P"/Users/fukamachi/Programs/lib/clack/src/"
;    #P"/Users/fukamachi/Programs/lib/clack/t/"
;    #P"/Users/fukamachi/Programs/lib/clack/tmp/")

;; ホームディレクトリ以下のMP3ファイルを再帰的に検索
(walk-directory
  #p"~/"
  (lambda (file)
    (when (string-equal (pathname-type file) "mp3")
      (fresh-line)
      (princ file))))
;-> /Users/fukamachi/nagaku.mp3
;   /Users/fukamachi/narunode.mp3
;   /Users/fukamachi/shoryaku/shimasu.mp3
;   ...

HTTPリクエストを投げたい - Drakma

 LispでHTTPリクエストを投げるには「Drakma」を使います。

 http-requestにURLを与えるとコンテンツ、HTTPステータスコード、HTTPヘッダなどが多値で返ってきます。以下はGoogle翻訳のAPIを叩いてみたサンプルです。

(import '(drakma:http-request))

(multiple-value-bind (body status header)
    (drakma:http-request "http://ajax.googleapis.com/ajax/services/language/translate?v=1.0&langpair=en%7Cja&q=Hello%2C%20World")
  (format t "~&Status: ~A~%Body: ~A~%Header: ~A~%"
          status body header))
;-> Status: 200
;   Body: {"responseData": {"translatedText":"こんにちは、世界"}, "responseDetails": null, "responseStatus": 200}
;   Header: ((CACHE-CONTROL . no-cache, no-store, max-age=0, must-revalidate) (PRAGMA . no-cache) (EXPIRES . Fri, 01 Jan 1990 00:00:00 GMT) (DATE . Thu, 07 Apr 2011 12:10:42 GMT) (CONTENT-TYPE . text/javascript; charset=utf-8) (X-BACKEND-CONTENT-LENGTH . 80) (X-EMBEDDED-STATUS . 200) (X-CONTENT-TYPE-OPTIONS . nosniff) (X-FRAME-OPTIONS . SAMEORIGIN) (X-XSS-PROTECTION . 1; mode=block) (SERVER . GSE) (CONNECTION . close))
;=> NIL

 bodyがJSONで返ってきているので、これをパースすれば英語を翻訳した結果だけを受け取れそうです。パースはCL-JSONを使うのが丁寧でしょうが、今回はそこまでする必要も感じないのでCL-PPCREで翻訳部分だけ抜き取ります。

(ppcre:scan-to-strings
 "(?<=translatedText\":\")[^\"]*"
 (nth-value 0
  (drakma:http-request "http://ajax.googleapis.com/ajax/services/language/translate?v=1.0&langpair=en%7Cja&q=Hello%2C%20World")))
;=> "こんにちは、世界"

 余裕がある人はGoogle翻訳する関数を書いてみるといいかもしれません。

単体テスト - CL-TEST-MORE

 自動化されたテストがないプロダクトはレガシーだと言われるようになったのはどうも最近のように思われますが、Lispは意外にも自動テストに関して熱心な傾向があります。

 単体テストフレームワークもいくつか選択肢があります。ここでは「CL-TEST-MORE」というPerlのTest::Moreというモジュールに影響されたライブラリを紹介します。

 関数などはほぼTest::Moreと同じです。

(import '(cl-test-more:is cl-test-more:plan cl-test-more:deftest cl-test-more:run-test-all))

(plan 9)

;; check if first argument is true
(ok (eq got expected) "Description")

;; check if "got" equals "expected"
(is got expected "Description")
(isnt got expected "Description")
;; with :test function
(is got expected "Description" :test #'string=)

;; rather than print *standard-output* "# This is just a comment\n"
(diag "This is just a comment")

;; macro expansion
(is-expand (got macro) (expected :like "this") "Description")

;; output
(is-print (write-line "aiueo") "aiueo\n" "Description")

;; functions always pass or fail
(pass "Description")
(fail "Description")

(finalize)

 出力結果はみんな大好きTAP(Test Anything Protocol)です。

ユーティリティ集 - Alexandria

 最後に、Common Lispのユーティリティ集についてです。

 もし「Haskellの○○はCLにはないのか…」とか「こんな関数が標準的にあったらいいのになぁ」と思ったら、自分で実装する前に「Alexandria」を探すといいです。AlexandriaはCLの標準ユーティリティ集的存在を目指したものです。

 欲しいコマンドはaproposで探します(それかac-slime)。

(apropos "plist" :alexandria)
;->  ALEXANDRIA.0.DEV:ALIST-PLIST, Def: FUNCTION
;    ALEXANDRIA.0.DEV:DELETE-FROM-PLIST, Def: FUNCTION
;    ALEXANDRIA.0.DEV:DELETE-FROM-PLISTF, Def: MACRO FUNCTION
;    ALEXANDRIA.0.DEV:DOPLIST, Def: MACRO FUNCTION
;    ALEXANDRIA.0.DEV:HASH-TABLE-PLIST, Def: FUNCTION
;   ALEXANDRIA.0.DEV::MALFORMED-PLIST, Def: FUNCTION
;                     MAPLIST, Def: FUNCTION
;   ALEXANDRIA.0.DEV::PLIST
;    ALEXANDRIA.0.DEV:PLIST-ALIST, Def: FUNCTION
;    ALEXANDRIA.0.DEV:PLIST-HASH-TABLE, Def: FUNCTION
;    ALEXANDRIA.0.DEV:REMOVE-FROM-PLIST, Def: FUNCTION
;    ALEXANDRIA.0.DEV:REMOVE-FROM-PLISTF, Def: MACRO FUNCTION
;                     SYMBOL-PLIST, Def: FUNCTION
;=> nil

 僕はplistが好きなのでplist系の関数群や、with-gensymsなどの汎用的なマクロをよく使っています。

まとめ

 この他にも、こういったことがしたければこのライブラリだろう、というものはいくつもあります。面倒なので紹介はしませんが以下に名前だけ載せておきます。

 また余談ですが、Quicklispのダウンロード数ランキングも公開されています。何かの参考になるかもしれません。

 さて、今回はCommon Lispライブラリを紹介しました。ライブラリの使用に関しては(Quicklispがあれば)特別困ることもないと思います。

 この連載は松山さんと交互に書いているため、今後話題が交互することになるでしょう。次の僕の回ではモダンなCommon Lispライブラリの作り方について説明します。

4/07/2011

第3回 SLIMEの使い方 基礎編

前回はQuicklispによるライブラリ管理について説明しました。今回はSLIMEの 基本的な使い方について説明します。

SLIMEとは

SLIMEは(Common) Lispのための統合開発環境(IDE)です。他のIDEとは異なり、 対話的にプログラムを構築できるのが特徴です。一度ハマれば他の開発環境は 使えなくなるでしょう。詳しい情報は次のURLを参照してください。
http://common-lisp.net/project/slime/

インストールと設定

SLIME

SLIMEのCVSスナップショット(本家推奨)をインストールします。OSは GNU/LinuxやMac OS XなどのUNIX系OSを想定しています。
$ wget http://common-lisp.net/project/slime/snapshots/slime-current.tgz
$ tar xvzf slime-current.tgz
$ mv slime-* ~/.emacs.d/slime
展開したディレクトリはslime-2011-04-07のような名前になりますが、ここ ではslimeという名前で~/.emacs.d/直下に移動します。
次にEmacsの設定を行います。次のコードを~/.emacsに書いてください。
;; Clozure CLをデフォルトのCommon Lisp処理系に設定
(setq inferior-lisp-program "ccl")
;; ~/.emacs.d/slimeをload-pathに追加
(add-to-list 'load-path (expand-file-name "~/.emacs.d/slime"))
;; SLIMEのロード
(require 'slime)
(slime-setup '(slime-repl slime-fancy slime-banner slime-indentation))
これで基本的な設定は完了です。
2011/7/10追記
若干設定項目が足りなかったので補足します。
;; SLIMEからの入力をUTF-8に設定
(setq slime-net-coding-system 'utf-8-unix)
2013/09/20追記
slime-indentationを追加しました。これで後述のcl-indent-patchesの設定は不要です。

popwin.el

使い始めれば気付くと思いますが、SLIMEは事あるごとにEmacsのウィンドウを 分割したり、他のウィンドウのバッファを切り替えたりします。これではせっ かくの優れた開発環境も台無しです。そこで、拙作のpopwin.elをインストール することをお勧めします。popwin.elはウィンドウの分割等を極力抑制し、プロ グラマが快適に作業することを助けてくれる優れものです。
インストール方法や使い方は次のURLを参照してください。
http://d.hatena.ne.jp/m2ym/20110120/1295524932
インストールが完了したら次のコードを~/.emacsに書いてください。
;; Apropos
(push '("*slime-apropos*") popwin:special-display-config)
;; Macroexpand
(push '("*slime-macroexpansion*") popwin:special-display-config)
;; Help
(push '("*slime-description*") popwin:special-display-config)
;; Compilation
(push '("*slime-compilation*" :noselect t) popwin:special-display-config)
;; Cross-reference
(push '("*slime-xref*") popwin:special-display-config)
;; Debugger
(push '(sldb-mode :stick t) popwin:special-display-config)
;; REPL
(push '(slime-repl-mode) popwin:special-display-config)
;; Connections
(push '(slime-connection-list-mode) popwin:special-display-config)

ac-slime

ac-slimeはauto-complete.elのSLIME拡張です。SLIME(SWANKサーバー)の情報を 利用するため、非常に高精度のコード補完が行えます。
ac-slimeは次のURLから入手できます。
https://github.com/purcell/ac-slime
なおauto-complete.elをあらかじめインストールしておく必要があります。イ ンストールしていない方は次のURLを参照してください。
http://cx4a.org/software/auto-complete/index.ja.html
ac-slimeをインストールするには、先のURLからac-slime.elをダウンロードし、 load-pathの通ったディレクトリにコピーします。install-elispや auto-installがある場合は次のコードを評価することでインストールできます。
;; install-elisp
(install-elisp "https://github.com/purcell/ac-slime/raw/master/ac-slime.el")
;; auto-install
(auto-install-from-url "https://github.com/purcell/ac-slime/raw/master/ac-slime.el")
最後に~/.emacsに次のコードを書いてインストール完了です。
(require 'ac-slime)
(add-hook 'slime-mode-hook 'set-up-slime-ac)
(add-hook 'slime-repl-mode-hook 'set-up-slime-ac)
これで.lispファイルやREPLでauto-complete.elを使った自動コード補完が行え るようになりました。

cl-indent-patches.el

↓SLIMEに取り込まれたため、このcl-indent-patches.elの導入は不要です。(2013/09/20)
この節は2011/7/10に追記しました
Emacs標準のインデント機能には若干問題があります。特にloopマクロのイン デントが変です。例えば次のようなインデントになってしまいます。
(loop for x in lst
      if (oddp x)
      collect x)
本来は次のようにインデントされるべきです。
(loop for x in lst
      if (oddp x)
        collect x)
このあたりに気をきかせてくれるのがcl-indent-patches.elです。次のURLから 入手できます。
http://boinkor.net/lisp/cl-indent-patches.el
このファイルをload-pathの通ったディレクトリに配置してください。後は .emacsに次のような設定を書けばOKです。
(when (require 'cl-indent-patches nil t)
  ;; emacs-lispのインデントと混同しないように
  (setq lisp-indent-function
        (lambda (&rest args)
          (apply (if (memq major-mode '(emacs-lisp-mode lisp-interaction-mode))
                     'lisp-indent-function
                     'common-lisp-indent-function)
                 args))))

SLIMEの使い方

SLIMEの起動

インストールと設定が完了したらM-x slimeとやってみましょう。次のような 表示の*slime-repl ccl*というREPLバッファが表示されれば成功です。
CL-USER> 
SLIMEを使って開発するに際して、まずやることはこのM-x slimeです。これ を行わなければ、後で説明するエディタコマンドやインデント、コード補完、 その他諸々が全く機能しません。まずM-x slime、これだけ覚えておいてくだ さい。
なお表示されたREPLバッファはちょっとした確認などを行ったりする場合を除 いて基本的には使用しません。

SLIMEの操作

SLIMEを起動して次にすることはlispファイルを開くことです。試しに foo.lispなどの適当なファイルを開いてください。
lispファイルのメジャーモードはlisp-modeです。このモードには様々なキー が割り当てられていますが、今回はその中でも覚えておくべきキーバインドを 紹介します。

C-c C-c

C-c C-cは現在ポイントしているトップレベルフォームをコンパイルします。 例えば次のような関数を編集しているとします。
(defun f (a)
  "Hello, World")
ここでC-c C-cすると関数fがコンパイルされます。実際にやってみれば分 かると思いますが、上の関数をコンパイルすると、SLIMEは変数aが未使用で あると警告してくれます。もし警告があればそれを修正してC-c C-c、という のが基本的な開発サイクルになります。

C-c C-k

C-c C-kは現在のファイルをコンパイルしてロードします。トップレベルフォー ムを一つずつC-c C-cするのが面倒なときに重宝します。また、C-c C-c同 様、ソースコードに問題があれば警告してくれるので、ファイルの最終的な確 認にも利用できます。

C-c C-z

C-c C-zは現在接続しているサーバーのREPLバッファを表示してくれます。何 か確認したいときや、前回紹介したQuicklispでライブラリをロードしたいとき にC-c C-zします。例えばCL-TEST-MOREというライブラリをロードし忘れてい たとしたら、C-c C-zでREPLバッファを表示して、次のように入力します。
CL-USER> (ql:quickload :cl-test-more)
REPLは使いこなせば非常に強力なので是非活用してください。

困った時

より本格的な開発サイクルは次回で説明しますが、SLIMEにおけるEmacsの C-g的な存在としてM-x slime-restart-inferior-lispを紹介しておきます。
これは現在接続しているサーバーを再起動してクリーンな状態に戻すコマンド です。SLIMEで開発していると、変にシンボルがインターンされたり、おかしな 値の変数ができたりします。また、正しく動作しているとしても、実は古い変 数などが残っていて偶然動作しているだけだったりもします。そういった状態 をリセットするのにM-x slime-restart-inferior-lispは非常に便利です。是 非覚えておいてください。

まとめ

今回は「SLIMEの使い方 基礎編」ということで、SLIMEのインストールと設定、 および非常に基本的な使い方を紹介しました。実際のところ、今回の内容だけ でSLIMEで開発を行うにはかなり情報が不足しています。Common Lisperなら気 付いていると思いますが、パッケージの扱いなど、様々な重要な点を端折って います。次回以降、より実際的なSLIMEの使い方を紹介していきたいと思います。

3/09/2011

第2回 Quicklispによるライブラリ環境

 Common Lispのライブラリを利用するのに、以前はASDF-Installを使っていました。しかし、ASDF-Installは外部のシェルコマンドに依存するためWindowsでの利用が面倒だったり、接続先のサーバが落ちていてダウンロードできなかったり、ダウンロードできたとしてもビルドできないといったことが多くありました。

 これを解決するためにZach Beaneが開発したのがQuicklispです。現在BETA公開中で、いくつか制限事項はありますが、誰でも利用できるようになっています。

Quicklisp BETA

セットアップ

 まずはQuicklisp自身をインストールします。以下のリンクからQuicklispをダウンロードしてください。

 あなたのLisp処理系 (きっとSBCL以外でしょうね) を起動し、以下のコードを実行してください。

(load "quicklisp.lisp")
(quicklisp-quickstart:install :path ".quicklisp/")
(ql:add-to-init-file)

※注) もしプロキシを通す必要があれば:proxyキーワードにURLを文字列で渡してください。例: (quicklisp-quickstart:install :path ".quicklisp/" :proxy "http://192.168.xx.xx:8080")

 これでセットアップは完了です。~/quicklispというディレクトリができていることを確認してください。

 もしインストール後にQuicklispのディレクトリを変更したい場合には、~/quicklispディレクトリを移動したあと、処理系のinitファイル (Clozure CLならば~/.ccl-init.lisp)の一部を修正します。

;;; The following lines added by ql:add-to-init-file:
#-quicklisp
(let ((quicklisp-init (merge-pathnames ".quicklisp/setup.lisp"
                                       (user-homedir-pathname))))
  (when (probe-file quicklisp-init)
    (load quicklisp-init)))

ライブラリのインストール・ロード

 Quicklispでライブラリのインストールをするにはql:quickloadを使います。このコマンドは試験に出るのでよく覚えておいてください。

(ql:quickload :cl-ppcre)

 指定したライブラリがまだインストールされていなければ、サーバからダウンロードしてインストールします。ASDF-INSTALLと違って依存パッケージも同時にインストールしてくれるので非常に助かります。

ライブラリを探す

 Quicklispに登録されているライブラリを探すにはql:system-aproposを使います。

(ql:system-apropos "web")
;-> #<system cl-webdav / cl-webdav-0.2.0 / quicklisp 2011-02-19>
;   #<system hh-web-tags / hh-web-tags-20110219-hg / quicklisp 2011-02-19>
;   #<system hh-web-tags-tests / hh-web-tags-20110219-hg / quicklisp 2011-02-19>
;   #<system symbolicweb / symbolicweb-20101207-git / quicklisp 2011-02-19>
;   #<system symbolicweb-examples / symbolicweb-20101207-git / quicklisp 2011-02-19>
;   #<system symbolicweb-jquery / symbolicweb-20101207-git / quicklisp 2011-02-19>
;   #<system webactions / portableaserve-20101006-cvs / quicklisp 2011-02-19>
;   #<system weblocks / weblocks-20110219-hg / quicklisp 2011-02-19>
;   #<system weblocks-demo / weblocks-20110219-hg / quicklisp 2011-02-19>
;   #<system weblocks-demo-popover / weblocks-20110219-hg / quicklisp 2011-02-19>
;   #<system weblocks-elephant / weblocks-20110219-hg / quicklisp 2011-02-19>
;   #<system weblocks-elephant-demo / weblocks-20110219-hg / quicklisp 2011-02-19>
;   #<system weblocks-memory / weblocks-20110219-hg / quicklisp 2011-02-19>
;   #<system weblocks-postmodern / weblocks-20110219-hg / quicklisp 2011-02-19>
;   #<system weblocks-prevalence / weblocks-20110219-hg / quicklisp 2011-02-19>
;   #<system weblocks-s11 / weblocks-20110219-hg / quicklisp 2011-02-19>
;   #<system weblocks-scripts / weblocks-20110219-hg / quicklisp 2011-02-19>
;   #<system weblocks-store-test / weblocks-20110219-hg / quicklisp 2011-02-19>
;   #<system weblocks-yarek / weblocks-20110219-hg / quicklisp 2011-02-19>
;   #<system weblocks-yui / weblocks-20110219-hg / quicklisp 2011-02-19>
;=> nil

リポジトリをアップデートする

 Quicklispのリポジトリは毎月1回更新されます。ライブラリが更新されたり、追加されたり削除されたり。最新のライブラリにアップデートするには以下のコマンドを実行します。

(ql:update-all-dists)

ASDF2 - 登録されていないライブラリを使う

 Quicklispには既に多くのライブラリが登録されているとはいえ、まだ登録されていないライブラリもあります。また、自分が作ったライブラリやアプリケーションのロードには使うことができません。

 そこで、「ASDF2」を使ってローカルの指定のライブラリをロードできるようにします。ASDF2は多くの処理系で標準バンドルされているはずなので特にセットアップの必要はありません。

 まずはライブラリを置くディレクトリを作ります。ここでは~/.lisp/systemsにライブラリを置くものとします。

$ mkdir ~/.lisp/systems

 そして~/.config/common-lisp/source-registry.conf.d/01-add-local-lisp.confに上のディレクトリパスを指定します。

;; ~/.config/common-lisp/source-registry.conf.d/01-add-local-lisp.conf
(:tree (:home ".lisp/systems"))

 これで準備完了です。ASDF2は~/.lisp/systemsの中を再帰的に見て、その中のライブラリをロードできるようになりました

ライブラリのインストールと利用

 たとえばClackを使うには~/.lisp/systemsにソースをcloneします。

※追記(2011/04/11): 現在ではClackはQuicklispに取り込まれているため、インストールするためにこの処理は必要なくなっています。

$ cd ~/.lisp/systems
$ git clone git://github.com/fukamachi/clack
$ git clone git://github.com/arielnetworks/cl-annot

 Lisp処理系を立ち上げ、ql:quickloadしてください。

(ql:quickload :clack)

 これでClackが使えるはずです。以下のコードを実行して、http://localhost:5000/にアクセスしてみてください。"Hello, Clack!"と表示されるはずです。

(clack:clackup
  #'(lambda (req)
      (declare (ignore req))
      '(200 nil ("Hello, Clack!"))))

 使えなかった場合は@nitro_idiotに報告してくださいね。

まとめ

 今回はQuicklispとASDF2によるライブラリの導入について解説しました。PerlやRubyに比べるとそれほど良い環境とは言えませんが、以前と比べてLispのライブラリ環境も悪くなくなってきています。これから数もどんどん増えていくことでしょう。

 次回は松山さんがEmacsとSLIMEの環境設定について書いてくれるはずです。

3/07/2011

第1回 Common Lisp処理系のインストール

同僚の深町さんと「モダンCommon Lisp」シリーズを初めることになりました。今回はCommon Lisp処理系のインストール方法について解説します。

どの処理系を使うか

Common Lispには処理系が多数存在するため、プログラミングを初める前に、まずどの処理系を使うか決めなくてはなりません。商用ならAllegro CLでほぼ一択のようですが、お金がかかりますし、プロプライエタリソフトウェアですから、今回は候補から除外します。

無料で手に入る(フリーな)処理系としてはSBCLが有名です。多くのライブラリでサポートされている処理系であり、コンパイラの品質が高い(速いコードが生成される)という強みを持っていますが、スレッドサポートがかなり怪しいらしく(未確認)、またコンパイルに非常に時間がかかるという問題を持っています(開発のテンポに影響)。

SBCL以外には、例えばCLISPなどがありますが、今回は深町さんに教えてもらったClozure CLをおすすめしようと思います。

Clozure CLは、SBCLほどではないにしろ、多くライブラリでサポートされている処理系であり、また元々のサポートの対象であったDarwin系のOS以外にも、GNU/LinuxやFreeBSD、Solaris、Windowsに対応しています。深町さんはMac OS X 10.6で、僕はUbuntu 10.10でClozure CLを使っていますが、今までで致命的な問題になったことはないです。また、SBCLに比べて非常にコンパイルが速いのも重要な利点です。ただ、生成されるコードの品質はそれほど良くないので、開発時はClozure CL、運用時はSBCL、という使い分けが良いと思います。

Clozure CLをインストールする

※追記1: Clozure CLに加えて今後の連載で紹介するEmacs、SLIME、Quicklispもすべて含めたLispboxというプロダクトもあります。

※追記2: Macを使っていて、Homebrewを導入している人はbrew install clozure-clで最新のClozure CLをインストールできます。

現在での最新安定板はv1.7です。一応、次のURLから最新版を確認してください。

http://trac.clozure.com/ccl

ここではGNU/Linux x86_64にClozure CL v1.7をインストールする手順を説明します。Mac OS Xでもほぼ同じ手順でインストールできると思います。

まず~/optにリリースをダウンロードします。

$ mkdir -p ~/opt
$ cd ~/opt
$ # Mac OS Xならdarwinx86
$ # Linuxならlinuxx86
$ # 詳しくは上記URLを参照
$ svn co http://svn.clozure.com/publicsvn/openmcl/release/1.7/linuxx86/ccl

cclディレクトリが作られ、その中に32bit用と64bit用の実行可能ファイルが置かれます。なぜSubversionなのかというツッコミはやめましょう。

実行スクリプトはscriptsディレクトリの中にccl (64bit用はccl64) という名前で入っています。実行する際はCCL_DEFAULT_DIRECTORYに、checkoutしたcclディレクトリのパスを設定してください。

このスクリプトをPATH環境変数の通ったディレクトリにcclという名前で保存します。ここでは~/binに置きます。

$ cp scripts/ccl ~/bin/ccl
$ chmod +x ~/bin/ccl

最後にターミナルでcclを起動して、正しく動作すればOKです。

$ ccl
Welcome to Clozure Common Lisp Version 1.7-r14925M (LinuxX8632)! ?

次回はQuicklispのインストール方法について解説します。