孤独にそっくり

開いている窓の前で立ち止まるな

実践Common Lispを読む 第3章

快門先決/raw檔
色とりどりのSLIME

こんにちは。
実践Common Lispの第3章を読みました。

第3章 実践:簡単なデータベース

とりあえず、『実践Common Lisp』っていうなら実践してみせろ!って言われるかもしれないから実践しました、とのことです。
第27章で扱われる「MP3ソフトウェアプロジェクト」の一環でもあるらしいです。
本章では、Common Lispの機能を概観するように基本的なリストからマクロまで、一通りやります。このページ数でマクロまでたどり着いて、なおかつわかりやすく書かれているのはすごいです。

3.1 CDとレコード

CDのメタ情報を属性リストを使って作る
あとdefunで関数も定義してみる

(defun make-cd (title artist rating ripped)
   (list :title title :artist artist :rating rating :ripped ripped))

;こんな感じのリストができる
(:Title "Rockin' the Surbs" :Artist "Ben Folds" :Rating 6 :Ripped t)
3.2 CDのファイリング

グローバル変数*db*を定義してレコードをデータベースに加える関数を書く

(defvar *db* nil)
(defun add-record (cd) (push cd *db*))
3.3 データベースの中身を見てみる

表示が見ずらいのでformatを使って*db*の中身をダン

(defun dump-db ()
  (dolist (cd *db*)
    (format t "~{~a:~10t~a%~}~%" cd)))
3.4 ユーザインタラクションを改善する

入力がユーザにやさしくないから改善しよう
read-lineを使ったり、parse-integer関数と:junk-allowed使ってみたりする

(defun prompt-read (prompt)
  (format *query-io* "~a: " prompt)
  (force-output *query-io*)
  (read-line *query-io*))

(defun prompt-for-cd ()
  (make-cd
   (prompt-read "Title")
   (prompt-read "Artist")
   (or (parse-integer (prompt-read "Rating") :junk-allowed t) 0)
   (y-or-n-p "Ripped [y/n]:")))
3.5 データベースの保存と読み出し

with-open-fileマクロ使うよ

(defun save-db (filename)
  (with-open-file (out filename
                       :direction :output
                       :if-exists :supersede)
    (with-standard-io-syntax
      (print *db* out))))

(defun load-db (filename)
  (with-open-file (in filename)
    (with-standard-io-syntax
      (setf *db* (read in)))))
3.6 データベースにクエリを投げる

remove-if-not関数とlambda使うよ

(defun select (selector-fn)
  (remove-if-not selector-fn *db*))

(defun where (&key title artist rating (ripped nil ripped-p))
  #'(lambda (cd)
     (and
      (if title (equal (getf cd :title) title) t)
      (if artist (equal (getf cd :Artist) artist) t)
      (if rating (equal (getf cd :rating) rating) t)
      (if ripped-p (equal (getf cd :Ripped) ripped) t))))
3.7 既存のレコードを更新

mapcarでlambdaする

(defun update (selector-fn &key title artist rating (ripped nil ripped-p))
  (setf *db*
        (mapcar
         #'(lambda (row)
             (when (funcall selector-fn row)
             (if title (setf (getf row :title) title))
             (if artist (setf (getf row :Artist) artist))
             (if rating (setf (getf row :Rating) rating))
             (if ripped-p (setf (getf row :Ripped) ripped)))
         row) *db*)))

(defun delete-rows (selector-fn)
  (setf *db* (remove-if selector-fn *db)))
3.8 ムダを排除して勝利を収める

この章題ちょっと面白い
同じようなコードが多いのでマクロを使って抽象化
バッククオートとloopも使うよ

(defun make-comparison-expr (field value)
  `(equal (getf cd ,field) ,value))

(defun make-comparisons-list (fields)
  (loop while fields
       collecting (make-comparison-expr (pop fields) (pop fields))))

(defmacro where (&rest clauses)
  `#'(lambda (cd) (and ,@(make-comparisons-list clauses))))
マクロの本質は、自動的なコードジェネレータだ!!!

バッククオート時の「,」と「@」の違い

`(and ,(list 1 2 3))→(AND (1 2 3))
`(and ,@(list 1 2 3))→(AND 1 2 3)

実例で示してくれるからとてもわかりやすい

まとめ

第3章はCommon Lispの大まかな機能を使ってデータベース作ってます。
Land of Lispは紹介した機能だけで面白い事してたけど、それとはまた違った感じです。
全体的によく理解できたのでサクッと次の章に行きたいと思います。
それにしても、Emacs+SLIMEが使いやすすぎてびっくりしました。
自動補完も入れたので、もう言うことないですね。
(僕の考えていることを予知しているのか、ってくらい的確に補完されます)
使い始めて2日目でこんなに感動するとは…。
次回から詳細な文法に踏み込んでいくみたいです。それでは。

追記

フランス料理の本に載っているレシピを1年ですべて再現してブログに書くという内容の
映画「ジュリー&ジュリア」がとても面白かったです。
全体的におしゃれだし、ジュリーは可愛い、ジュリアは面白い、でいい映画でした。
僕も同じようなことしているなと思いながら見てましたけど、コード載せたり、内容書くのはあんまりよくない気もする…

追記2

Emacsでバックアップファイルが大量に作られるの(「#」と「~」だけど、特に「~」)が煩わしのでググりました
Emacsを快適に使うファイル周りの設定紹介 - Qiita
~/.emacs.d/init.elにbackup.elの内容をぶち込んだら「~」が出なくなったのでめでたしめでたし。
こうしてEmacsの設定はどんどん拡張されていくんですね( ˘ω˘)スヤァ