Hatena::Groupcadr

わだばLisperになる このページをアンテナに追加 RSSフィード

2004 | 12 |
2005 | 01 | 02 | 07 | 10 | 11 |
2006 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2007 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2008 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2009 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2010 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2011 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 11 |

2007-12-31

Practical Common Lisp (17)

| 12:10 | Practical Common Lisp (17) - わだばLisperになる を含むブックマーク はてなブックマーク - Practical Common Lisp (17) - わだばLisperになる

引き続きPractical Common Lisp 第4章4. Syntax and Semanticsを読んでメモしてみています。やっと4章が終った。この先が思いやられる。

Practical Common Lisp を読む - mokeheheのScheme日記 - sicpさんもPCL読書記録開始とのことで、xyzzyで取り組まれるとのこと。多分、日本でCommon Lisp系で一番ユーザが多い処理系は、xyzzyじゃないでしょうか。xyzzy周辺は面白いことをやってる人も凄い人も多い気がします。MP3のストリーミングサーバとか後半戦はきつそうですが、xyzzyは結構色々拡張してる人が沢山いて想像以上になんでもできるっぽいので、どういう風になっていくのか楽しみです。

Formatting Lisp Code

  • コードの整形というものは、厳密には文法にも機能にも関係はないものの、コードのスムースな読み書きには重要な事項。
  • Lispコードについていえば、まず重要なのはインデント
  • インデントはLispのコードの機能的構造が良く反映されていて、対応する括弧を勘定したりなどという無駄が発生しないようにすべき。
  • 一般には、コードがネストする場合、インデントは一段深くなる。改行が必要な場合は、同レベルは同じ深さで揃えられる。例えば、下の例ように。

(some-function arg-with-a-long-name
               another-arg-with-an-even-longer-name)

  • マクロとスペシャルフォームは、すこし違っていて、本体部が、2つのスペースで字下げされる。

(defun print-list (list)
  (dolist (i list)
    (format t "item: ~a~%" i)))

  • とはいえ、インデントについてあれこれ悩む必要はなく、SLIMEのような開発環境は適切に字下げの面倒を見てくれる。エディタのようなソフトウェアがインデントを解析する場合、非常に容易に解析できるのがLispの文法のアドバンテージでもある。
  • SLIME(Emacs)についていえば、開き括弧に移動して、C-M-qを押下すると、字下げし直してくれる。関数定義全体は、C-c M-qで整形してくれる。
  • インデントについてのエディタの支援はただ見た目を綺麗にしてくれるという点だけではなく、タイポも発見しやすくしてくれる。たとえば、下記のようなコードがあったとして

(defun foo ()
  (if (test)
    (do-one-thing)
    (do-another-thing)))

  • testの後に括弧を忘れた場合、下記のようにインデントされるため発見が容易になる。

(defun foo ()
  (if (test
       (do-one-thing)
       (do-another-thing))))

  • 他にコードの整形で重要なこととしては、閉括弧はリストの最後にくっつけること。下記のようには書かないこと。

(defun foo ()
  (dotimes (i 10)
    (format t "~d. hello~%" i)
  )
)

  • 最後の)))の連続はそら恐しく感じるが、適切にインデントされたLispコードの括弧は邪魔に意識されることはない筈。
  • 最後にコメントの付け方。1〜4つのセミコロンを使い分け、各々の範囲によって数が変わる。

;;;; Four semicolons are used for a file header comment.

;;; A comment with three semicolons will usually be a paragraph
;;; comment that applies to a large section of code that follows,

(defun foo (x)
  (dotimes (i x)
    ;; Two semicolons indicate this comment applies to the code
    ;; that follows. Note that this comment is indented the same
    ;; as the code that follows.
    (some-function-call)
    (another i)              ; this comment applies to this line only
    (and-another)            ; and this is for this line
    (baz)))

どうでも良いメモ

CLtLでも註釈について書いてて上記と大体同じなんですが、行を継続させる場合は、一文字下げるとか書いてます。それで、一つの;の場合は、スペースなしで直接書き始めてる感じです。最近のコードではあんまり見掛けることは無いんですが、7、80年代のMITのコードではその慣習で書いてることがたまにあります。何れにせよコメントの付け方ってあんまりこだわって書いてる人も多くない気はしますが…。


;;;; Four semicolons are used for a file header comment.

;;; A comment with three semicolons will usually be a paragraph
;;; comment that applies to a large section of code that follows,

(defun foo (x)
  (dotimes (i x)
    ;; Two semicolons indicate this comment applies to the code
    ;; that follows. Note that this comment is indented the same
    ;; as the code that follows.
    (some-function-call)
    (another i)              ;this comment applies to this line only
    (and-another)            ; and this is for this line
    (baz)))

という風です。殆ど間違い探し的にしか違いがありませんが…。

お題: コマンドライン引数の取得、年間カレンダー

| 10:01 | お題: コマンドライン引数の取得、年間カレンダー - わだばLisperになる を含むブックマーク はてなブックマーク - お題: コマンドライン引数の取得、年間カレンダー - わだばLisperになる

最近、アルゴリズム的な問題ばっかりだー、という意見がでてから、急にパズル的でない問題が増えてきました。

(118)コマンドライン引数

コマンドライン引数の取得は、処理系依存なので、KMRCLの紹介を投稿して終了。

良く考えれば、Common Lispでは処理系依存の処理を切り分ける記述ができるので、できるだけ多くの処理系をフォローするようなコードを書いた方が良かったと反省。

まあ、今からでも調べて投稿はできるんですが(笑)

(119)年間カレンダー

年間カレンダーを表示させる問題でこれもパズル的ではない問題。

他の言語の方をみてみると20行位で書いてました。自分には20行で書くのは無理だなーと思いつつ挑戦。

Date-Calcが使えそうだったので使ってみましたが、

無駄が多くて無理矢理気味な感じになってしまいました。

25行程度に収まったので、とりあえず良しとします。

2007-12-29

DEFPACKAGEでパッケージ名を#:fooと書くのはなぜなんだぜ?

| 03:28 | DEFPACKAGEでパッケージ名を#:fooと書くのはなぜなんだぜ? - わだばLisperになる を含むブックマーク はてなブックマーク - DEFPACKAGEでパッケージ名を#:fooと書くのはなぜなんだぜ? - わだばLisperになる

comp.lang.lispで、A Question about DEFPACKAGE syntaxというDEFPACKAGEに関しての疑問の投稿があり結構面白いと思ったので、勝手にまとめてみることにしました。

内容はというと、defpackageでパッケージ名を"FOO"と書いたり、:fooと書いたり#:fooと書いたりするのはなんでなのでしょう、というもので、自分も前から疑問に思ってはいました。自分の理解では、defpackageはマクロなので、macroexpandしてみると

(defpackage #:foo (:use #:foo) (:export #:bar))
;==>
(EVAL-WHEN (:COMPILE-TOPLEVEL :LOAD-TOPLEVEL :EXECUTE)
  (SB-IMPL::%DEFPACKAGE "FOO" 'NIL 'NIL 'NIL 'NIL '("FOO") 'NIL 'NIL '("BAR")
                        '("FOO") 'NIL 'NIL (SB-C:SOURCE-LOCATION)))

というように、名前は、(symbol-name :foo)的に展開されるので、"FOO"も、:fooも、#:fooも同じで#:fooだとキーワードパッケージにもインターンされないから潔癖症の人はこっちが好きなんじゃないか、とか思ってましたが、フォローの流れをみると大体それで合ってたようで良かったです。

  • Pascal Costanza氏
    • おいっすー!、#:fooも:fooも同じだけど、P-COSたんイン(ターン)されないので、#:fooはGCの対象になると思うお!
  • Edi Weitz氏
    • もともとの基本は、"FOO"だけれど、大文字をタイプするのが面倒なので:foo。#:fooという表記はキーワードの節約。以前は#:fooと書いていたが:fooの方が一文字少ないので、最近は:fooの方が好き。

などなど。その他、Common Lispの処理系が大文字なのが嫌なので、

(setf *print-case* :downcase)

してるよ、に対してのレスで、いや、

(setf (readtable-case *readtable*) :invert)

の方が良いんじゃね?等々、あまり関係のない流れも。

Common Lispであんまり衛生的ではないdefine-syntax

| 02:03 | Common Lispであんまり衛生的ではないdefine-syntax - わだばLisperになる を含むブックマーク はてなブックマーク - Common Lispであんまり衛生的ではないdefine-syntax - わだばLisperになる

2月にはGauche本が出版されるとのことで、2月位からSchemeの風が吹きまくると思われ、来年は、ちょっとしたSchemeブームになると思うのです。

On Lispを買いそびれている、Schemeのことはあんまり知らない、どっちかっていうとCommon Lispしか知らない自分でさえ買っちゃおう、と思っているのですから絶対来ると思うのです。

それで、Common Lispですが、Schemeブームのお蔭でそれなりに耳目も集まると思うのです。

Common Lispもなんとはなしに盛り上がると良いですね。

自分もGauche本買ったら、Common Lispで試してみたいと思います(*'-')

そんなSchemeですが、SchemeにはCommon Lispみたいなマクロの機構以外にパターンマッチングをベースにした衛生的なマクロ機構がR5RSで規定されています。

これのCommon Lispがあったので試してみました。

作者は、t-y-schemeのDorai Sitaram氏で配布元は、Scheme Macros for Common Lispです。

これを見付けた時には、これで、SRFI-42を移植しよう!とか思ったのですが、実際に色々試してみるとそんなにするするっと達成できることでもなさそうです。

これを使うと、


(define-syntax aif
  (syntax-rules ()
    ((aif expression then else) (let ((it expression))
				  (if it then else))))
    ((aif expression then) (let ((it expression))
			     (if it then)))))

のようにほとんど、まんまR5RSのdefine-syntaxのようにマクロが書けます。

ちなみに...はCommon Lisp的に実用的でないので、デフォルトは***に設定されています。

SRFI-42はとりあえず置いて、練習としてSRFI-26のcutを移植してみました。


(defpackage :srfi-26
  (:use :cl :mbe)           ;mbeとしてパッケージを作って取り込んでみています。
  (:export :cut :cute))

(in-package :srfi-26)

(define-syntax srfi-26-internal-cut
  (syntax-rules (<> <...>)

    ;; construct fixed- or variable-arity procedure:
    ;;   (begin proc) throws an error if proc is not an <expression>
    ((srfi-26-internal-cut (slot-name ***) (proc arg ***))
     (lambda (slot-name ***) (funcall (progn proc) arg ***)))
    ((srfi-26-internal-cut (slot-name ***) (proc arg ***) <...>)
     (with ((rest-slot (gensym)))
       (lambda (slot-name *** &rest rest-slot) (apply proc arg *** rest-slot))))

    ;; process one slot-or-expr
    ((srfi-26-internal-cut (slot-name ***)   (position ***)      <>  . se)
     (with ((x (gensym)))
       (srfi-26-internal-cut (slot-name *** x) (position *** x)        . se)))
    ((srfi-26-internal-cut (slot-name ***)   (position ***)      nse . se)
     (srfi-26-internal-cut (slot-name ***)   (position *** nse)      . se))))

; exported syntax

(define-syntax cut
  (syntax-rules ()
    ((cut . slots-or-exprs)
     (srfi-26-internal-cut () () . slots-or-exprs))))

殆ど、まんまでOKで、LISP-1と、LISP-2の違いを手動で修正すれば良いだけという感じなのですが、残念ながら、MBEは、define-syntaxが衛生的と言われる所以でもある変数補足の問題までは全自動で解決できないので、(gensym)を使って回避する必要があります。(with 〜)で囲んでgensymと置き換える訳なのですが、これの見極めがちょっと難しい。馴れてないということもあるんでしょうが、どれが補足される可能性があるのか普通のマクロより難しい気がします。

ということで、一応動くことは動くんですが、これで良いのやらという感じです。

しかしこのマクロはかなり凄い気がするので、仕組を理解できたら良いなとは思っています。

;; 動作
;(cut cons (+ a 1) <>) 	is the same as 	(lambda (x2) (cons (+ a 1) x2))
(let ((a 3))
  (funcall (cut #'cons (+ a 1) <>) 3))
;==> (4 . 3)

;(cut list 1 <> 3 <> 5) 	is the same as 	(lambda (x2 x4) (list 1 x2 3 x4 5))
(funcall (cut #'list 1 <> 3 <> 5) 2 4)
;==> (1 2 3 4 5)

;(cut list) 	is the same as 	(lambda () (list))
(funcall (cut #'list))
;==> nil

;(cut list 1 <> 3 <...>) 	is the same as 	(lambda (x2 . xs) (apply list 1 x2 3 xs))
(funcall (cut #'list 1 <> 3 <...>) 2 4 5 6 7)
;==> (1 2 3 4 5 6 7)

;(map (cut * 2 <>) '(1 2 3 4))
(mapcar (cut #'* 2 <>) '(1 2 3 4))
;==> (2 4 6 8)

;(map (cut vector-set! x <> 0) indices)
(let ((x (make-array '(5) ) )
      (indices '(1 3 0 2 4)))
  (flet ((vector-set! (vec idx obj)
	   (setf (aref vec idx) obj)))
    (mapc (cut #'vector-set! x <> 0) indices)
    x))
;==> #(0 0 0 0 0)

;(for-each (cut write <> port) exprs)
(let ((exprs '("foo" "bar" "baz") ) )
  (mapc (cut #'write <> :stream *standard-output*) exprs))
;==> "foo" "bar" "baz"

;(map (cut <> x y z) (list min max))
(let ((x 1) (y 2) (z 3))
  (mapcar (cut <> x y z) (list #'min #'max)))
;==> (1 3)

;(for-each (cut <>) thunks)
(let ((thunks (list (lambda () (print "hello") )
		   (lambda () (print "world")))))
  (mapc (cut <>) thunks))
;==>"hello" "world" 

2007-12-28

SBCL 1.0.13リリース

| 01:36 | SBCL 1.0.13リリース - わだばLisperになる を含むブックマーク はてなブックマーク - SBCL 1.0.13リリース - わだばLisperになる

SBCLは大体毎月25日にリリースされるんでしょうか、今月も27日に新バージョンがリリースされました。

リリースノートなどは普段全く読みませんが、たまたま読んでみたら、run-programの動作が強化されたということで、

enhancement: RUN-PROGRAM allows unicode arguments and 
environments to be used (using the default stream external
 format), and allows non-simple strings to be used.
(thanks to Harald Hanche-Olsen)

とのこと。これまた、たまたまですが、昨日PLEACの問題を書いているときに

(let ((output 
       (with-output-to-string (out)
	 (sb-ext:run-program "date" '() 
			     :output out
			     :search '("/bin" "/usr/bin")
			     :environment '("LANG=ja_JP.utf-8")))))
  (princ output))

というものの出力が、

2007年 12月 29日 土曜日 00:58:28 JST

となってしまい、うーん、と思っていました。

1.0.13で試してみると、

2007年 12月 29日 土曜日 00:54:24 JST

とエンコードが合っていればきちんと表示されます。(例はUTF-8で揃えています)

この辺は、KMRCLでは問題なかったので、PLEACではKMRCLを使いました。(まあ、処理系依存度が低くなるので、KMRCLを使ったほうが良いとは思います…)

それで、全然脈絡はありませんが、SBCLのソースからのビルド、普段使っているイメージの作成までの記録をつけて置くことにしてみました。

以下メモです。

  1. ソース取得

$ wget http://nchc.dl.sourceforge.net/sourceforge/sbcl/sbcl-1.0.13-source.tar.bz2

  1. 解凍

$ tar jxvf sbcl-1.0.13-source.tar.bz2

  1. マルチスレッドで使いたいのでカスタマイズファイルcustomize-target-featuresに記述

$ cat ./customize-target-features.lisp

(lambda (features)
      (flet ((enable (x)
               (pushnew x features))
             (disable (x)
               (setf features (remove x features))))
        ;; Threading support, available only on x86/x86-64 Linux, x86 Solaris
        ;; and x86 Mac OS X (experimental).
        (enable :sb-thread)))
  1. ビルド

$ cat ./sbclcompr

#!/bin/sh

SBCL_HOME=/usr/local/sbcl/1.0.12/lib/sbcl /usr/local/sbcl/1.0.12/bin/sbcl $*

$ sh make.sh "./sbclcompr"

  1. ビルド後のテスト

$ cd tests && sh ./run-tests.sh

  1. ドキュメントをビルド

$ cd doc/manual && make

  1. インストール

$ INSTALL_ROOT=/usr/local/sbcl/1.0.13 sh ./install.sh

Practical Common Lisp (16)

| 00:21 | Practical Common Lisp (16) - わだばLisperになる を含むブックマーク はてなブックマーク - Practical Common Lisp (16) - わだばLisperになる

引き続きPractical Common Lisp 第4章4. Syntax and Semanticsを読んでメモしてみています。

Truth, Falsehood, and Equality

  • Common Lispを修得に関して重要な真偽値の扱いについて
  • シンボルNILは唯一の偽値であり、それ以外は、全部真とみなされる。
  • 非NILの値を返したい場合、正準的にはシンボルTが使われる
  • NILで唯一ややこしい事項として、アトムでもありリストでもある唯一のオブジェクトであるということが挙げられる。
  • NILは、偽を表わすとともに、空リストも表わす。
  • リーダが()を読み込む時それは、NILとして扱われる。これらは完全に相互に交換可能。
  • 前述したように、NILは定数となっているので、'NILも、'()もNILと同等ということになる。クウォートされていない場合は、シンボルNILが定数NILを参照するものとして評価される。しかし、クウォートされている場合QUOTEスペシャルオペレータは、直接シンボルNILを評価することになっている。
  • TについてもNILと同様のことがいえる。'TもTも同じ。
  • 「同じもの」という言葉は「同じものというのは、どういうことか」が定義されていなければいけないが、Common Lispでは、「同じもの」を判定するためにいくつか用意されているものがある。
  • 「=」は数が同値であるかを判定する。「CHAR=」は文字が同一であるかを判定する。
  • このセクションでは、一般的に2つのLispオブジェクトが渡された場合に、同等のものかを判定する4つの機構を紹介する。段階的にEQ、EQL、EQUAL、EQUALPがある。
  • EQは、Lispオブジェクトが同一のものかどうかを判定する。
    • 残念ながら、数と単体の文字の同一性についてどのように扱うかは処理系の判断に委ねられている。
    • (eq 3 3)は真であっても偽であってもどちらでも良いということ。(eq X X)という式で、Xに数字か単一文字かが来る場合、eqを用いて判定するのはまずい。
    • 特定の処理系では意図した通りに動くかもしれないが、全部の処理系で成り立つとは限らない。また、処理系間の違いだけでなく、同一の処理系であっても、eqの仕様が変更されるのは別段問題はないことに注意。
  • ということから、同じクラスの同一の数字、同一の文字であることを判定するには、eqlを使うことになっている。
    • (eql 1 1)は真となり、(eql 1 1.0)は、整数クラスと、浮動小数点数クラスを比較しているので偽となる。
  • EQとEQLの使い分けについての流派は2つある。
  • 「可能な限りEQを使う派」数と文字を主張比較することにはならないことを確信して使う必要がある。
    • (a)しかし、文脈上で数と文字を比較することにはならないことを主張することができる。
    • (b)EQをつかった方がほんのすこし効率的になる(同一のLispオブジェクトかどうかを判定するだけのため)
  • 「EQL常用派」
    • (a)EQが現われた時にEQが正しく使われているかを確認する必要がないので、コードが明解になる
    • (b)EQとEQLの間の「すこしの効率」の違いは、他のボトルネックの要素と比べると誤差のようなもの
  • この本で示されるコードは、「EQL常用派」コードです。
  • より広い範囲のものの同一性を判定するには、EQUALやEQUALPを使う。
  • EQLよりわずかに判定基準が広く、EQLでは偽となり比較できないものを比較するのに使う。
  • これらの基準は、過去のLispプログラマが実用上便利だなと思って定めた以上のものではないので、必要とあらば、自分自身で判定のための述語を作成して使おう。
  • EQUALにリストが与えられた場合、再帰的にEQUALを適用して内容を調べ、構造と内容物が一緒の場合、真となる。
  • また、文字列の場合は、含まれている文字を逐次比較してが全部同じならば、真となる。後述するビットベクタとパスネームストリングについても同様。
  • これら以外は、EQLに判定を投げる。
  • EQUALPはEQUALよりさらに判定基準がゆるい。
    • 文字列中の大文字と小文字を区別の規準としない。
    • 数字の数としての価値が同じならば、同じものとして扱う。つまり、(equalp 1 1.0)は真。
    • 対応要素がそれぞれEQUALP等価なリスト同士は、リストとしてもEQUALP等価
    • 対応要素がそれぞれEQUALP等価な配列同士も同じ。
    • これら以外は、EQLに判定を投げる。

2007-12-27

PLEACの空き地

| 18:39 | PLEACの空き地 - わだばLisperになる を含むブックマーク はてなブックマーク - PLEACの空き地 - わだばLisperになる

どう書く.orgはマルチリンガルなクックブックも狙っているそうなのですが、そういえば、そのものズバリのマルチリンガルクックブックの実現を目指しているサイトが確かあった筈と思い出し、検索してみたところ、Perlのクックブックのレシピを色んな言語で実装しようというPLEAC - Programming Language Examples Alike Cookbookというサイトがみつかりました。

自分の記憶でも確かこのサイトです。

Common Lispのエントリもあって、現在は30%強の達成度で、SchemeはGuileでのエントリとなりますが50%弱の達成度です。

割と面白そうなので、空き地をみつけて挑戦してみることにしました。

とはいえ、できそうなところだけですが…。

16章の「プロセス管理とプロセス間通信」ってところが0%なので、なんとなくこれにあたってみることにします。

レシピ16.1 プログラムの出力を収集する

;; $output = `program args`;   # collect output into one multiline string
;; ----------------------------------------------------------------

(let ( (output (kmrcl:command-output "ls -l")))
  output)
;; command-outputはformatのように書ける。
(kmrcl:command-output "~A ~A" "/bin/ls" "-l")

;; @output = `program args`;   # collect output into array, one line per element
;; ----------------------------------------------------------------
(let ( (output (ppcre:split "\\n" (kmrcl:command-output "ls -l"))))
  output)

シェルコマンドの出力を取り込むということですが、KMRCLパッケージのcommand-outputが使えるので、それで。

配列への格納は、CL-PPCREのSPLITを使って改行文字で分解して一つのリストに纏めるということにしてみました。これには色んな方法があるとは思います。

レシピ16.2 別のプログラムを起動する

;; $status = system("vi $myfile");
(let ( (myfile "bar.txt"))
  (let ( (status (kmrcl:run-shell-command "vi ~A" myfile)))
    status))

viを起動するってのは、色々面倒なことが多いかもしれませんが、ls等であればまず問題なくいけると思います。

レシピ16.4 別のプログラムと読み書きする

;; 文字限定:出力を読む
;; ----------------------------------------------------------------
(let ( (cmd "ls -l"))
  (with-input-from-string (readme (kmrcl:command-output "ls -l"))
    (series:iterate ( (line (series:scan-stream readme #'read-line)))
    #|
     行を処理
    ...|# ))

;; 文字限定:文字列をcmdに渡す
;; ----------------------------------------------------------------
(let ( (cmd "tr 'a-z' 'A-z'")
      (tempfile (format nil "/tmp/~A" (gensym "tempfile-"))))
  (with-open-file (out tempfile :direction :output)
    #| 
    (print "foo" out)
    (print "bar" out)
    (print "こんにちは" out))
    ...|#
  (kmrcl:awhen (probe-file tempfile)
    (prog1 (kmrcl:command-output "cat ~A|~A" tempfile cmd)
           (delete-file kmrcl:it))))

別のプログラムというのは、シェルで実行するコマンドなわけですが、文字列限定ってことで書いてみました。

どうも難しい。どういう書法が定石なんでしょうか。バイナリの読み書きもどうしたもんかと。

レシピ16.11 名前付きパイプを使ってプロセスをファイルのように見せる

(defun pipe-reader (pipe &optional (output *standard-output*))
  (with-open-file (fifo pipe)
    (series:iterate ( (line (series:scan-file fifo #'read-line)))
      (write-line line output))))

(defun pipe-writer (mesg pipe)
  (with-open-file (fifo pipe :direction :output :if-exists :append)
    (write-line mesg fifo)))

(let ( (named-pipe "/tmp/named.pipe"))
  (kmrcl:run-shell-command "mkfifo ~A" named-pipe)
  ;; 別スレッドで待ち受け
  (princ "Got: ")
  (portable-threads:spawn-thread 'pipe (lambda ()
                                         (pipe-reader named-pipe #.*standard-output*)))
  ;; FIFO経由で書き込み
  (pipe-writer "Smoke this." named-pipe)
  (force-output))

パイプへ書き込む関数と読み出す関数を定義して、別スレッドで実行することにしてみました。

変なところで改行されたりされなかったりしますが、表示のさせ方を考えれば多分大丈夫なのではないかと。

まとめ

という感じで、できそうなところに挑戦してみました。

いやいや、そういう書き方は変だよ、とか、こう書くとスマートだよね、とか、これ実装してみたよ、等々ありましたら、グループで専用の掲示板を作ってみましたので、良かったら、お気軽に書き込んでみて下さい!(→cadr group) もちろんここへのトラックバックも大歓迎ですし、直接PLEACに投稿してみるというのも良いと思います。自分は、数が溜ったら投稿してみようかなと思っています。(PLEACのメーリングリストに参加して、コードをレビューしてもらって掲載という流れのようです。)

2007-12-26

Practical Common Lisp (15)

| 05:14 | Practical Common Lisp (15) - わだばLisperになる を含むブックマーク はてなブックマーク - Practical Common Lisp (15) - わだばLisperになる

引き続きPractical Common Lisp 第4章4. Syntax and Semanticsを読んでメモしてみています。

Macros

  • スペシャルオペレータは関数呼び出しの方法を拡張したものではあるが、それは言語の標準で定められたものでもある。
  • 一方、マクロはユーザに文法を拡張する手段を提供する。
  • 3章で見たように、マクロはS式を引数としてとって任意のLispフォームへ展開し、展開された場所で評価される仕組となっている。
  • マクロの評価は2段階に分かれる
    1. まず、マクロのフォームは、中身を評価されることなくマクロ展開機構に渡される
    2. マクロ展開機構から展開された結果のフォームが返されるので、それを通常の評価方法で評価する。
  • この2段階で処理されるということを心にとめておくことは重要。REPLで対話的に操作していると、あっという間に処理が終ってしまうので、このことを見失いがち。
  • しかし、Lispのコードがコンパイルされる時には、完全に別々の2段階を踏んで実行されるので、きちんと把握することが重要。
  • 例えば、COMPILE-FILE関数で、一つのソースコードファイルを丸ごとコンパイルする場合、そのファイルの中のマクロは再帰的にすべて展開され、関数とスペシャルフォームだけが含まれたものまで展開される。
  • マクロ抜きされたものが、LOAD関数でロードできるFASL形式のファイルへとコンパイルされる。これは、コンパイルされはするもののその際に実行はされない。
  • マクロ展開がコンパイル時に行われるということは、コンパイルされたファイルをロードする際には、マクロを展開するという労力を払わなくても良いということを意味する。
  • また、評価器がマクロ展開機構に展開される前のフォームを評価することはないので、マクロのフォームは正規のLispのフォームの形をしている必要もない。
  • 各々のマクロは、させたいことを展開してS式に割り当てる手段を提供する、換言すれば、各々のマクロはローカルな文法を持つことが可能ということでもある。
  • 例えば、3章で例に上げた、さかさフォームのマクロは、正規のLispフォームがさかさまになったものを与えられれば、正規のLispフォームを返すという文法を定めていることになる。
  • マクロが果す役割としては、コンパイラへのフックの提供という目的もある。文法的には関数呼び出しと非常に類似しているのだが、提供するものはまったく違う。

2007-12-25

お題: ワーカスレッドを安全に終了させるまで待機

| 05:10 | お題: ワーカスレッドを安全に終了させるまで待機 - わだばLisperになる を含むブックマーク はてなブックマーク - お題: ワーカスレッドを安全に終了させるまで待機 - わだばLisperになる

今回もアルゴリズム云々ではないので、何とか試作する位はできそう、ということで挑戦。

メインのスレッドがあって、スレッドプールからスレッドを拾ってきて仕事をさせて、子供のスレッドが全部終了するのを確認して次の仕事へ…というようなものを作れとのこと。

うーん。マルチスレッド関係なのでPortable-Threadsが使えるとは思うものの、スレッドプールをどうしたもんかと。

結構ググったりしてみたけれど、スレッドの使い回しの情報はみつけられず、とりあえずスレッドプールを使ってない方も何名かいらしたので自分も使わないということにして、投稿。

Common Lispでの定番スレッド処理ってどんな感じなんでしょう。The Common Lisp Cookbook - Threadsも結構複雑です。

まあ、自分は、そもそもスレッド処理自体分かってなかったりしますが…。

何か題材を見付けて色々練習してみよう。

INTERLISP的Xmas

| 23:38 | INTERLISP的Xmas - わだばLisperになる を含むブックマーク はてなブックマーク - INTERLISP的Xmas - わだばLisperになる

昨日突然、INTERLISPの処理系の新バージョン登場というふれこみで、comp.lang.lispにINTERLISP系の処理系のタレこみがありました。

タレこんだ人は、作者さん本人で、Blake McBrideさんなのですが、INTERLISP系というのは珍しいので、早速ダウンロードして試してみました。

Windowsと32bit Linuxだと素直に使えます。

INTERLISPには馴染みが薄い自分はなんとも言えませんが、大体普通のINTERLISPと同じ感じです。

INTERLISPにはマクロがなくその代わりに、引数を評価しないで取り込む、NLAMBDAという仕組があるのですが、この辺の使い方がちょっと違うかもしれません。

NLAMBDAはどんなもんなのかというと、

((nlambda (x y z) (list x y z)) foo bar baz)
;=> (foo bar baz)

というものです。

確かにこの仕組があれば、MACLISP系のマクロに相当するものは書けそうです。

ということで、色々試してみましたが、Lispf4では良く分からなかったので、

確認のため、PDP-10のINTERLISP-10でDOLISTを作ってみることにしました。

defineq (
    (dolist (nlambda l
	     ((lambda (var list body tag l)
		(eval `(prog (,var)
			     (setq ,l ,list)
		        ,tag (cond ((null ,l) (return)))
			     (setq ,var ,(car l))
			     ,@body
			     (setq ,l (cdr ,l))
			     (go ,tag))))
	      (caar l) (cadar l) (cdr l) (gensym) (gensym)))) )

という感じに作ってみましたが、EVALを呼んだりしてところなどがちょっと違うんじゃないかなあ、と思っています。

もう少し資料を探さなくては…。Lispf4でも大体近い感じなのですが、バッククオート等は使えないようです。うーん。どうするのが定石なのか。

文章の流れとは全然関係ないのですが、今日は12/25日ということもあり、INTERLISP-10の処理系を起動すると、

@lisp
INTERLISP-10  31-Dec-84 ...
Merry Christmas.
3_

と挨拶してくれます。

いつもは、Hi.だけなのですがクリスマスや、ハロウィンなどでメッセージが変わるようです。

地味にいい味出してます。

2007-12-24

Practical Common Lisp (14)

| 00:20 | Practical Common Lisp (14) - わだばLisperになる を含むブックマーク はてなブックマーク - Practical Common Lisp (14) - わだばLisperになる

引き続きPractical Common Lisp 第4章4. Syntax and Semanticsを読んでメモしてみています。

Special Operators

  • 全部が全部、関数の形式で解決できるというわけではない。
    • 関数の引数は、関数に渡される前に評価済みになっていなくてはならないため
  • IFを例にとって詳細を解説 (if x (format t "yes") (format t "no")) というフォームを考える。
  • 仮にIFが関数だとすると、引数は左から右に順に評価されることになる。この場合だと、formatの返り値は、NILとなり、X、NIL、NILが関数に渡され処理されることになる。しかし、それだとIFが真偽を分岐させる前にyesもnoも表示されてしまうことになる。
  • この問題を解決するために、スペシャルオペレータが導入されている。Common Lispの処理系には、そのようなものが25用意されている。
  • もし、リストの最初のシンボルがスペシャルオペレータならば、残りの引数の評価方法はそのスペシャルオペレータが規定する評価順序に従う。
  • IFの場合だと、最初の引数を評価して、それが、NILでないならば、その次を評価し、結果を返す。それ以外は、一つ飛して3番目を評価し、結果を返す。(if test-form then-form [ else-form ])
  • test-formは常に評価され、then-formが評価されるか、else-formが評価されるかする。
  • 同様に単純なスペシャルオペレータとして、QUOTEがある。一つの引数を取り、単純にそれを返す。しかし評価はされない。(+ 1 2)というリストが渡された場合、3ではなく、(+ 1 2)を返す。 (quote (+ 1 2))
  • QUOTEは、広く使われるので、リーダに簡便な記法が組込まれ用意されている
  • (quote (+ 1 2))と書く代わりに、'(+ 1 2)と書ける。
  • この文法は、リーダーによって解釈されるS式のちょっとした拡張になっているので、どちらの記法をしても、結果を渡される評価器からすれば、最初が、QUOTEで残りが(+ 1 2)というリストということになる。
  • 一般的に言えば、スペシャルオペレータは、評価器による特別な解釈を要求する。幾らかのスペシャルオペレータは変数環境等の環境を操作するものがある。例えば、LETは、新しい変数の束縛をつくる。それら一部は、6章で詳細を検討する。
  • (let ((x 10) ) x)は、10が評価値となるが、これは、2番目に出てくるxが評価された値となっている。そのxは、(x 10)として束縛されたxの値となっている。

2007-12-23


Practical Common Lisp (13)

| 16:59 | Practical Common Lisp (13) - わだばLisperになる を含むブックマーク はてなブックマーク - Practical Common Lisp (13) - わだばLisperになる

引き続きPractical Common Lisp 第4章4. Syntax and Semanticsを読んでメモしてみています。

Function Calls

  • 関数呼び出しのフォームは簡単で、関数名以降の残りのリストを評価して得られた値を関数に渡すだけ。
  • ということは、関数名以降も正規のLispフォームである必要があるということ。
  • 基本形 (関数名 引数*)
  • (+ 1 2)がどう評価させるかを順に辿ると
    1. 1が評価される。
    2. 2が評価される。
    3. これらを、+に渡して処理し、結果として3が返る。
  • もう少し複雑な、(* (+ 1 2) (- 3 4))の場合
    1. (+ 1 2)は、上記より3であることが分かり、(- 3 4)は、-1で、これらが*に渡り、結果として2になる。
  • というように、他の言語では、文法上、個別に特別な形をしているような処理も、Lispでは上記のようになんでも関数処理の流れとして扱い、一貫したものになっていたりする。

お題: 法演算

| 06:03 | お題: 法演算 - わだばLisperになる を含むブックマーク はてなブックマーク - お題: 法演算 - わだばLisperになる

一時期、集中的に熱中してどう書く.orgに参戦して、自作のうんこちゃんクオリティのプログラムを投下していましたが、アルゴリズムを捻り出すような問題が多くなってきてしまったので、元が捻り出せない自分は挫折しておりました。 でも、やっぱり勉強になるのでできそうなもの位は投稿してみることにしました。 今回は、法演算ということですが、問題を見にいった時にschemeでの解答を見てしまったため、もう、それ以外の方法の解答は考えられなくなってしまい、結局パクリになりました。
(defmacro with-mod (div &body body)
  `(%with-mod ((+ - * /) ,div) ,@body))

(defmacro %with-mod (((&rest fns) div) &body body)
  (let ((g (gensym)))
    `(let ((,g (labels ,(mapcar
                         (lambda (fn)
                           `(,fn (&rest expr) 
                                 (mod (apply (symbol-function ',fn) expr) ,div)))
                         fns)
                 ,@body)))
       (if (numberp ,g) (mod ,g ,div) ,g))))
という解答でしたが、 %with-modは、fletにするべきでした。再帰してる訳でもないしsymbol-functionもいらなくなるし…。 こういうのは大体投稿してから気付くという。
(defmacro %with-mod (((&rest fns) div) &body body)
  (let ((g (gensym)))
    `(let ((,g (flet ,(mapcar
		       (lambda (fn)
			 `(,fn (&rest expr) 
			       (mod (apply #',fn expr) ,div)))
		       fns)
                 ,@body)))
       (if (numberp ,g) (mod ,g ,div) ,g))))
どう書く.orgのCommon Lisp部は、一時賑やかだったみたいですが、自分が参加し始めた9月頃からは、常駐組は恐らく1、2名程になってしまいました。もしかしたら、どう書く.org自体への参加者が減ってるのかもしれませんが…。ちなみに、Common Lisp部は自分が全体のレベルを下げておりますので、参加する際の敷居は低いと思われます。

2007-12-22

45年前の処理系 元祖LISP 1.5 を試してみる

| 06:41 | 45年前の処理系 元祖LISP 1.5 を試してみる - わだばLisperになる を含むブックマーク はてなブックマーク - 45年前の処理系 元祖LISP 1.5 を試してみる - わだばLisperになる

先日は、PDP-1のエミュレータで、PDP-1 Lispを動かしてみましたが、やっぱり元祖である、LISP 1.5も動かしてみたいところ。

でも、さすがに、50年前のIBM7094の環境はかなり厳しいだろうと思いつつ、調べてみたら、なんとこれも簡単に動かせるようにまとめている方がいました。

約50年前の環境が再現できるというのは色々な意味で凄い。まず、データが保存されていて、今日のコンピュータで読めるってのが凄い。

こちらのページに詳しい方法が記載されています。

手短にまとめると、準備するものとしては、

が必要です。

上記のファイルを揃えて設定すれば、IBM 7094でLISP 1.5のバッチジョブを流せます。

しかし、若干面倒なので、Emacsのcompileを使って、バッチ処理のシェルスクリプトを起動させてみることにしました。

とはいえ、適当なでっち上げなので、試されたい方は各自作成されると良いと思います。

自分の環境では、lisp1.5:loadというcompileをラップした関数と、シェルスクリプトの組み合わせで*compilation*バッファに結果が表示されるようにしてみました。

  • バッチ用シェルスクリプト(load-lisp1.5)
#!/bin/sh

INFILE=$1
EMUDIR=~/lisp/lisp1.5-linux/

cd $EMUDIR
[ -f sys.log ] && rm sys.log
cat head.job $INFILE tail.job >cur.job
./txt2bcd -s cur.job lisp.job.mt
./i7094 lisptapeboot.ini
cat sys.log
  • head.job
       TAPE    SYSTMP,B3
       TAPE    SYSTAP,A4
       TAPE    SYSPOT,A3
       TAPE    SYSPPT,A7
       TEST  WHATEVER
  • tail.job
STOP)))   )))   )))   )))
       FIN      END OF LISP RUN
  • Emacsのcompile関数をラップしたバッチ処理を呼び出す関数
(defun lisp1.5:load ()
  (interactive)
  (let ((compile-command (concat "~/bin/load-lisp1.5 " (buffer-file-name))))
    (compile compile-command)))

これで若干対話的にコードが書けるようになったので、適当に試してみます。

  • reverseとmapcarを作ってみる
define ((
(r1 (lambda (m l)
      (cond ((null l) m)
	    ((quote t) (r1 (cons (car l) m) (cdr l))))))

(reverse (lambda (u) (r1 () u)))

(mapcar (lambda (lst fn)
	  (prog (l res)
	     (setq l lst)
	     again
	     (cond ((null l) (return (reverse res))))
	     (setq res (cons (fn (car l)) res))
	     (setq l (cdr l))
	     (go again))))
))

mapcar((1 2 3 4)
       (quote (lambda (x) (plus 10 x))))

cond(((quote t) (print (quote hello))))

今日からみると色々変ったところが多いのですが、まず、トップレベルで関数に外側の括弧が付いてなかったりします。

これは、evalquoteと呼ばれるらしいですが、evalで評価するか、applyで評価するかと考えれば良い、と The Evolution of Lisp に書いてました。ちなみにInterlispでは、evalquoteの形式でコーディングできます。

それで、マニュアルを見る限りではreverseは標準で付いてくるっぽいのですが、見付からないので作ってみました。

mapcarの引数の順番が関数とリストとで逆ですが、元々は、リスト→関数の順番だったようです。これまたInterlisp系では、伝統に則ってCommon Lisp(Maclisp系)とは逆です。

  • 結果
MTA: unit is read only
LPT: creating new file

HALT instruction, PC: 10524 (TRA 10523)
Goodbye
             TAPE    SYSTMP,B3

   B3 IS NOW LISP SYSTMP.
             TAPE    SYSTAP,A4

   A4 IS NOW LISP SYSTAP.
             TAPE    SYSPOT,A3

   A3 IS NOW LISP SYSPOT.
             TAPE    SYSPPT,A7

   A7 IS NOW LISP SYSPPT.
             TEST  WHATEVER



  THE TIME ( 0/ 0  000.0) HAS COME, THE WALRUS SAID, TO TALK OF MANY THI
NGS .....   -LEWIS CARROLL-
 EVALQUOTE OPERATOR AS OF 1 MARCH 1961.    INPUT LISTS NOW BEING READ.


  THE TIME ( 0/ 0  000.0) HAS COME, THE WALRUS SAID, TO TALK OF MANY THI
NGS .....   -LEWIS CARROLL-
  FUNCTION   EVALQUOTE   HAS BEEN ENTERED, ARGUMENTS..
 DEFINE

 (((R1 (LAMBDA (M L) (COND ((NULL L) M) ((QUOTE T) (R1 (CONS (CAR L) M)
(CDR L)))))) (REVERSE (LAMBDA (U) (R1 NIL U))) (
 MAPCAR (LAMBDA (LST FN) (PROG (L RES) (SETQ L LST) AGAIN (COND ((NULL L
) (RETURN (REVERSE RES)))) (SETQ RES (CONS (FN (
 CAR L)) RES)) (SETQ L (CDR L)) (GO AGAIN))))))


 END OF EVALQUOTE, VALUE IS ..
 *TRUE*

  FUNCTION   EVALQUOTE   HAS BEEN ENTERED, ARGUMENTS..
 MAPCAR

 ((1 2 3 4) (QUOTE (LAMBDA (X) (PLUS 10 X))))


 END OF EVALQUOTE, VALUE IS ..
 (11 12 13 14)

  FUNCTION   EVALQUOTE   HAS BEEN ENTERED, ARGUMENTS..
 COND

 (((QUOTE T) (PRINT (QUOTE HELLO))))


 HELLO

 END OF EVALQUOTE, VALUE IS ..
 HELLO



  THE TIME ( 0/ 0  000.0) HAS COME, THE WALRUS SAID, TO TALK OF MANY THI
NGS .....   -LEWIS CARROLL-
 END OF EVALQUOTE OPERATOR
             FIN      END OF LISP RUN

なんでか知りませんが、ルイス・キャロルの文言が引用されてたりします。

若干わかりづらいですが、

END OF EVALQUOTE, VALUE IS ..

の後が評価結果です。なんとなく良い味を醸し出しています。

やっぱり手近に処理系があって手軽に試せるというのは良い!

PDFのマニュアルもあるので、これを見ながらLISP 1.5を探索してみるのも面白いと思います。

2007-12-21

Practical Common Lisp (12)

| 01:14 | Practical Common Lisp (12) - わだばLisperになる を含むブックマーク はてなブックマーク - Practical Common Lisp (12) - わだばLisperになる

引き続きPractical Common Lisp 第4章4. Syntax and Semanticsを読んでメモしてみています。

S-expressions As Lisp Forms

  • リーダがテキストをS式に変換して初めて評価器の出番となる。
  • リーダが拾って来たS式、全部を全部評価できるというわけではない。
  • 評価器はLispのフォームを決定する役割があるが、これは非常にシンプルなルールに基いている。

4-どんなアトム(リスト以外のものと空リスト)も正しいLispフォームで、また、シンボルが第一番目の要素になっているリストも正しいフォームとして扱われる。

  • アトムは、シンボルと、それ以外の二種類に分類できる。
  • 評価されるLispフォームとしてのシンボルは現在保持している値となる。
    • "変"数という名前(Variable)にも注意しよう。値が"変"わらない"変"数もあり、"Constant Variable"と呼ばれる。(日本語だと、定数っていうし問題なしじゃなかろうか、どうなんだろう) 例としてはPIがCommon Lispには定数として定義されている。
  • それ以外のアトムは、それ自体が評価された値になるものになる。"Hello, World"を評価すると"Hello, World"
  • シンボルでもそういう例は存在する。Tと、NILを評価してもTとNIL、他には、キーワードシンボルが該当する。":"から開始されているシンボル。:FOOを評価すると、:FOO
  • リストの評価方法について
    • シンボルが第一番目の要素のリストは正しい表現として扱われるが、しかし、そういうリストには三種類あり、それぞれ随分と違った方法で評価される
    • 最初のシンボルの名前が、関数であるか、マクロであるか、スペシャルフォームであるか、の三種
    • 以降、関数呼び出しフォーム、マクロフォーム、スペシャルフォームと呼ぶことにする。

うーん、この節は自分の英語力で読むにはちょっと難しいなー。

2007-12-20

40年前の処理系 PDP-1 Lisp を試してみる

| 05:24 | 40年前の処理系 PDP-1 Lisp を試してみる - わだばLisperになる を含むブックマーク はてなブックマーク - 40年前の処理系 PDP-1 Lisp を試してみる - わだばLisperになる

何となく前から気になっていたPDP-1 Lispを試してみることにしました。

PDP-1 Lispは、LISPの処理系の中でもかなり初期の部類で、1964年に当時高校生だった、L. Peter Deutschが作った処理系です。

その名の通り、PDP-1で動きます。

PDP-1のエミュレータはSIMHというエミュレータ集の中に含まれていることは以前から知ってはいたのですが、しかしさすがにPDP-1ということもあり、きっと動作させるのはかなり面倒なんだろうと思って放置していたのですが、環境が整備されていたこともあり実際に試してみると非常に簡単でした。

とりあえず用意するものとしては、

  1. PDP-1 エミュレータComputer Simulation and History
  2. Lisp for the PDP-1

位のものです。

simhは、ubuntuや、debianではパッケージになっているようです。

PDP-1 Lispの準備

lispswre.zipに同梱のlisp_doc.txtを読んで、必要なものを作成します。

といっても

展開したディレクトリで、

$ cc macro1.c -o macro1
$ macro1 lisp.mac

と実行するのみです。

起動スクリプトの作成

  • lisp.boot
set cpu mdv
load lisp.rim
d extm_init 1
run

d tw 7777
c

d tw 400
c

d ss 2
save lisp.sav
c

として作成してみます。

起動用スクリプトの作成

  • pdp-1-lisp
#!/bin/sh

cd /share/sys/emu/PDP-1.emu/
./pdp1 ./lisp.boot

適当に起動用のスクリプトを作ってみます。

とりあえず、これで遊べます。

(plus 3 3) |スペース| |改行|
;=> 6

式を入力して、スペースを入力し改行すると評価される様です。

Emacsと連携させてみる。

run-lispで使えるように適当に設定してみます。

(setq inferior-lisp-program "~/bin/pdp-1-lisp")

等々

inferior-lisp-modeだと、式の後の一文字スペースが足りないので、若干無理矢理ながら関数に細工します。といっても一文字空白を挿入してから改行するようにするだけです。

(defun lisp-eval-region (start end &optional and-go)
  "Send the current region to the inferior Lisp process.
Prefix argument means switch to the Lisp buffer afterwards."
  (interactive "r\nP")
  (comint-send-region (inferior-lisp-proc) start end)
  (comint-send-string (inferior-lisp-proc) " \n")
  (if and-go (switch-to-lisp t)))

不正な入力があると、PDP-1ごと落ちますが、simhのモニタに落ちたらcを入力すると復帰します。

SIMHの終了には、Control-Eを入力し、モニタに落ちて、qの入力で終了します。

Emacs内部の場合、Control-Qでエスケープする必要ありです。

色々探って遊ぶ

  • Hello, World!
(print (quote Hello\,\ World!))
;=> Hello, World! 

とりあえず、Hello, World! 文字列というものは無いようなので、シンボルです。シンボルの大文字と小文字を区別するというのが意外です。

  • 関数を定義してみる

関数の定義する方法が全く分からず、結構悩みました。

シンボルは、oblistというリストに格納されていて、起動時には、

(atom car cdr cond cons eq gensym greaterp list minus 
numberp stop prin1 quotient rplaca rplacd terpri prog return
 go setq sassoc plus times logand logor xeq loc null quote
 lambda apval subr expr fsubr fexpr t oblist read eval
 print nil) 

となっていてこれで全部です。

標準で使える関数もこのなかにあるということになるのですが、defineとかcsetqとかそれらしきものはなく、結構悩んだのですが、どうにもならないので、マニュアルを探したところPDFのマニュアルがありました。

これによると、どうやら関数は、rplacdを使って定義するらしいのです。

どういう仕組みなのかというと、シンボルのcdrがシンボルのプロパティになっていて、

(cdr (quote foo))
;=> nil

のようになります。それで、シンボルのCDR部に関数定義を詰め込むと名前に関数が設定できるという仕組みのようです。こんなの良く考えつくなと感心。

ということで、定番のfibは、

(rplacd (quote fib)
	(quote 
	 (expr
	  (lambda (x)
	    (cond ((greaterp 2 x) x)
		  (t (plus (fib (plus x (minus 1)))
			   (fib (plus x (minus 2))))))))))

(fib 10)
;=>25 (8進数)

と定義できました。再帰も使えます。引き算用の関数はないので負数を足すということになっていて、lesspもないのでgreaterpを逆転させてます。数字は8進数です。

exprというのは、固定の引数を取る関数であることを表わしています。他にfexprというのもありこっちは、可変引数です。

  • reverse
(rplacd (quote reverse)
	(quote (expr
		(lambda (lst)
		  (prog (l res)
		     (setq l lst)
		     loop 
		     (cond ((null l) (return res)))
		     (setq res (cons (car l) res))
		     (setq l (cdr l))
		     (go loop))))))

progでreverse

  • mapcar
(rplacd (quote mapcar)
	(quote (expr
		(lambda (f lst)
		  (prog (l res)
		     (setq l lst)
		     loop
		     (cond ((null l) (return (reverse res))))
		     (setq res (cons (f (car l)) res))
		     (setq l (cdr l))
		     (go loop))))))

(print
(mapcar (quote (lambda (x) (times 3 x)))
	(quote (1 2 3))))
;==>
;(3 6 11) 

なんとなくmapcarを作ってみたり。

なんとなくまとめ

「空リストが偽を表わす」というのはどの辺りから始まったのかというのは、あまりはっきりしていないようです。

最初期のLisp 1.5でも真と偽は、TとFとして定義されている様子て、「空リストが偽を表わす」ことも肯定的に捉えられてはいなかったようで、Interlisp文化以外のところでは、むしろ否定的に考えられていたようです。

それで、このPDP-1 Lispはどうなのかと言えば、()も、(quote ())もnilで偽ということになっています。

(cdr ni)はどうかといえば、(apval nil)がでてきます。これは、nilのプロパティってことなんでしょう。

ちなみに(cdr nil)としても、エラーにならず、nilを返すというのは、Interlisp由来らしいのですが、処理系の発展の流れからすると、PDP-1 Lisp -> BBN Lisp -> Interlispという流れで、Peter Deutschがからんでいるので、もしかしたらこの辺は、この人由来なのかしらと、ぼんやり考えたりしました。

2007-12-19

Practical Common Lisp (11)

| 22:07 | Practical Common Lisp (11) - わだばLisperになる を含むブックマーク はてなブックマーク - Practical Common Lisp (11) - わだばLisperになる

引き続きPractical Common Lisp 第4章4. Syntax and Semanticsを読んでメモしてみています。

S-expressions

  • 基本的なS式は、リストとアトムで構成される
  • リストは、括弧で区切られていて、任意の数の空白で区切られた要素を含むことができる。
  • アトムはそれ以外のすべて
  • セミコロンは、コメント記号で、それ以後改行までコメントとして扱われる
  • ということで、文法的に覚えるべきことは非常に少ない。あとはアトムの種類を覚えることくらいになる。
  • アトムには、数字、文字列、名前(シンボル名)がある。
  • 数字は、そのものずばり、数字で構成されたもの。+と-記号を付けて表記できる。小数点と、有理数の表記のための/、そして、指数表記もつかえる。
123       ; the integer one hundred twenty-three
3/7       ; the ratio three-sevenths
1.0       ; the floating-point number one in default precision
1.0e0     ; another way to write the same floating-point number
1.0d0     ; the floating-point number one in "double" precision
1.0e-4    ; the floating-point equivalent to one-ten-thousandth
+42       ; the integer forty-two
-42       ; the integer negative forty-two
-1/4      ; the ratio negative one-quarter
-2/8      ; another way to write negative one-quarter
246/2     ; another way to write the integer one hundred twenty-three
  • これらの数字は、整数、有理数、浮動小数点数と呼ばれるが、独自の表記方法を持つ、複素数も表現できる。これは10章で解説する。
  • 同じ数字を色々な表現で書くことが可能だということが分かるが、処理系が簡略化するので注意が必要。
  • -2/8や、246/2は、-1/4や、123となる。同様に、1.0と1.0e0は表記の違いということになる。一方、1.0と、1.0d0は違うものとして扱われるが、整数と、浮動小数点数は違うものとして扱われるため。詳細は、10章で。
  • 文字列は以前の章でダブルクオートに囲まれたものとして表現として目にしている。ダブルクオートを中に含めるには、\を直前に付けてエスケープする。エスケープして表現する必要があるのは、ダブルクオート(")と、バックスラッシュ(\)の二つのみ。
"foo"     ; f、o、oを含む文字列
"fo\o"    ; 同上
"fo\\o"   ; f、 o、 \、oで構成された文字列
"fo\"o"   ; f、 o、 "、oで構成された文字列
  • 名前(シンボルの名前という意味?)はLisp内のプログラムの名前として使用している、FORMATや、hello-worldや、*db*など。これらは、シンボルと呼ばれ、処理系は、名前自体が、変数を指しているのか、関数を指しているのか等々は気にしない。
  • 殆どすべての文字がシンボル名に使えるが空白文字は句切りとして使用されているため使えない。数字も使えるが、数字のみで構成されたものは数字自体と区別がつかないので駄目。また、ピリオド(.)も名前につかえるが、ピリオド(.)一文字のみというのは駄目。他に使えない文字というのは10種あり、()'"`,:;\|は使えない。
  • これらの文字を使いたい場合、そのままでは使えないが、\を前に置くか、||で囲むかすれば使える。
  • 重要事項として、リーダは文字の大文字と小文字を区別しないということがあり、全部大文字として扱う。Fooもfoo、FOOも同じ。しかし、\f\o\oと、|foo|は、fooとして扱われ、FOOとは違ったオブジェクトとして扱われる。REPLでfooという名前を打ち込んで、FOOと返ってくるのはこのため。
  • 今日の一般的なコーディングスタイルでは、全部小文字で書くが、結局のところリーダは全部大文字として解釈している。
  • 同じ表記のは常に同じシンボルとして扱われる。
  • リーダは、文字列を全部大文字に直し、シンボルとして取り込もうとする。その際にパッケージの名前一覧に同じ名前がないかをチェックする。見付からなかった場合、新しく一覧に登録される。見付かった場合は、先に登録されていたオブジェクトが使い回される。同じ名前ならばどこでも同一のオブジェクト使われているということでもある。
  • Lispは、Algol系の言語より多くの文字種が使えるということもあるが、ハイフン(-)で区切られた名前(例hello-world)が良く使われているところが特徴的。
  • また、*が前後に付いた変数名は、大域変数を表わすという習慣がある。同じく定数表記は、+を前後に付けるのが慣用表現。内部的なものは%で開始されることが多い、%%から開始というのも同様。
  • Common Lispの標準関数/変数名には、(A-Z)に加え、*、+、-、/、1、2、<、=、>、&、が使われている。
  • 文法に占める比重とは、数字、文字列、シンボルが多いが、他には、ベクタ、単体の文字、アレイ表現がある。これらは、10、11章で扱う。
  • 要点としては、これらをどう組合されてS式が構成されているかということだが、簡単な図を示す。
x             ; Xというシンボル
()            ; 内容が空のリスト
(1 2 3)       ; 3つの数字を含むリスト
("foo" "bar") ; 2つの文字列を含むリスト
(x y z)       ; 3つのシンボルを含むリスト
(x 1 "foo")   ; 数字とシンボルと文字列を含むリスト
(+ (* 2 3) 4) ; シンボルと数字を含むリストと数字を含むリスト
  • もう少し複雑な例としては、全体として4つの要素があるリストで、2つはシンボル、1つは空リスト、1つは、シンボルと文字列を含むリストということになる。
(defun hello-world ()
  (format t "hello, world"))

2007-12-18

祝Pitmanual改訂版発表ということでdesetqをつくってみる

| 03:23 | 祝Pitmanual改訂版発表ということでdesetqをつくってみる - わだばLisperになる を含むブックマーク はてなブックマーク - 祝Pitmanual改訂版発表ということでdesetqをつくってみる - わだばLisperになる

どうしてなのかは知りませんが、急に12/16日の日曜日にMacLISPのマニュアルの改訂版(The Revised Maclisp Manual (The Pitmanual))が発表されました。

結構前からMACLISP infoというサイトはありまして、Maclispの情報が纏められるサイト予定地ということだったんですが、ずっとコンテンツは不在でした。

作者のKent M. Pitman氏は特にどっかに完成を発表したという訳でもないようでcomp.lang.lispにタレこみがあって初めて周知された様子。

しかし、仕上がりは結構気合いが入っていて、原稿をHTMLに直しただけでは全然なく、Common Lispとの比較や現在の視点からの考察が加えられています。

需要と供給のバランスからすれば、かなりの過剰供給っぷり。

製作には、奥さんと娘さんと本人が当たったということで、これまた不思議な家族。

ということで、なんとなく記念にMaclispのdesetqを作って遊んでみることにしました。

こないだ、Maclispのletの分割代入版を作ったときに、desetqも作ろうと思っていたのですが、こっちは放置してました。

desetqは、setqに分割代入機能が付いたものでPitmanualでの解説はThe Pitmanual: Control Formsです。

Pitman+Manualで、Pitmanual、これの前のDavid Moon氏が作ったのは、Moonualと呼ばれていたとのことで、その辺の文化を継承してるみたいです。

動作としては、

(let ((a 1) (b 2) (c 3) (d 4) (e 5) (f 6))
  (desetq (((a) b . c) d e f)  (list (list* (list a) b c) d e f) )
  (list a b c d e f))
;==> (1 2 3 4 5 6)

という感じになります。

(defmacro desetq (&rest bind-specs)
  (unless (evenp (length bind-specs))
    (error "Too many arguments in form ~S." bind-specs))
  (do ((l bind-specs (cddr l)) 
       body vars)
      ((endp l) `((lambda ,vars ,@body) ,@(mapcar (constantly ()) vars)))
    (let ((var (car l)) (val (cadr l)))
      (if (consp var)
	  (let ((tem (gensym)))
	    (multiple-value-bind (varlist vallist) (des- var tem)
	      (setq vars `(,@vallist ,@vars ,tem))
	      (setq body `(,@body (setq ,tem ,val) ,@varlist))))
	  (setq body `(,@body (setq ,var ,val)))))))

(defun des- (bind sym)
  (let (vars)
    (values 
     (labels ((frob (bind sym)
		(cond ((null bind) nil)	
		      ((atom bind)
		       `((setq  ,bind ,sym)))
		      ((null (car bind))
		       `((setq ,sym (cdr ,sym))
			 ,@(frob (cdr bind) sym)))
		      ((and (atom (car bind)) (null (cdr bind)))
		       `((setq ,(car bind) (car ,sym)))) ;last -1
		      ((atom (car bind))
		       `((setq ,(car bind) (car ,sym))
			 (setq ,sym (cdr ,sym))
			 ,@(frob (cdr bind) sym)))
		      ('T (let ((carcons (gensym)))
			    (push carcons vars)
			    `((setq ,carcons (car ,sym))
			      ,@(frob (car bind) carcons)
			      (setq ,sym (cdr ,sym))
			      ,@(frob (cdr bind) sym)))))))
       (frob bind sym))
     vars)))

  • 動作
(desetq (((a) b . c) d e f)  (list (list* (list a) b c) d e f))
;==>

((LAMBDA (#:G2 #:G1 #:G0)
   (SETQ #:G0 (LIST (LIST* (LIST A) B C) D E F))
   (SETQ #:G1 (CAR #:G0))
   (SETQ #:G2 (CAR #:G1))
   (SETQ A (CAR #:G2))
   (SETQ #:G1 (CDR #:G1))
   (SETQ B (CAR #:G1))
   (SETQ #:G1 (CDR #:G1))
   (SETQ C #:G1)
   (SETQ #:G0 (CDR #:G0))
   (SETQ D (CAR #:G0))
   (SETQ #:G0 (CDR #:G0))
   (SETQ E (CAR #:G0))
   (SETQ #:G0 (CDR #:G0))
   (SETQ F (CAR #:G0)))
 NIL NIL NIL)

と展開されます。

オリジナルのものはもうすこし綺麗に展開されるのですが、若干面倒なので、これで良しとしました。

Practical Common Lisp (10)

| 23:11 | Practical Common Lisp (10) - わだばLisperになる を含むブックマーク はてなブックマーク - Practical Common Lisp (10) - わだばLisperになる

引き続きPractical Common Lisp 第4章4. Syntax and Semanticsを読んでメモしてみています。文字ばっかりだと、英語が苦手な自分には何だか良く分かりません…。とりあえず、ウェブ翻訳で訳したりして適当に妄想してるメモになっております。

Breaking Open the Black Box

  • Lispの文法と意味論の詳細に立ち入る前に他の言語とはどういったところが違うのかを考察してみる。
  • 殆どの言語の処理系(コンパイラ、インタプリタ共に)はブラックボックスのようなものになっている。ユーザはテキストをそれに読み込ませて結果を受けとる。
  • ブラックボックスの詳細に立ち入るならば、もちろん沢山のパートに分けられる。字句解析や用いられるデータ構造等様々あるが、ブラックボックスたる所以は、その構造や機構が専らブラックボックス内部の処理プロセスで用いられるのみで、それ自体は、処理系製作者向けのものといった感じになっているところ。
  • Common Lispはちょっと違っていて、どう処理系を実装するかと、どう言語が定義されるかの二つが考慮された帰結として、一つのブラックボックスで処理されるのではなくて、二つのブラックボックスで二段階に処理される。テキストをLispのオブジェクトに変換するリーダが最初のもので、それを評価する評価器がそれに続く。
  • リーダは文字列をLispオブジェクトたるS式への変換を担当する。Lisp以外の言語でいうと処理系内部での構文木の生成にあたる。
  • 評価器は構築されたS式からLispの文法を定める。S式が全部正しいLispオブジェクトという訳ではない、例えば、(foo 1 2)、("foo" 1 2)は共にS式としては正しいが、Lispのフォームとしては、最初が文字列のリストはLispフォームとして意味を成していない。
  • ということで、リーダがどのようにS式を組み立てるか、と、評価器がどのように式を評価するのかの二つに焦点を当てて考察したい。

2007-12-17

Practical Common Lisp (9)

| 16:40 | Practical Common Lisp (9) - わだばLisperになる を含むブックマーク はてなブックマーク - Practical Common Lisp (9) - わだばLisperになる

やっとPractical Common Lisp 第4章4. Syntax and Semanticsに突入

この章もコードは少なく殆ど説明の様なので、箇条書に纏めることにしました。主に文法を説明する章のようです。

What's with All the Parentheses?

  • Lispの文法はAlgol系のものとは大分違う。
  • 括弧で囲まれた前置記法というのが特徴的。この記法については好き嫌いが分かれる。
  • McCarthyが最初にLispを発明した時、M式というAlgol風の記法も考えていて、実装しようと思っていたが、結局実装されず仕舞いだった。このことは、"Historical of Lisp"に詳しい。
  • コーディングには主にM式を使い、それが機械的にS式に変換され、それをマシンが実行するという流れを考えていたが、プログラマははS式を好んで使いはじめてしまい、M式が必要に迫られることはなかった。
  • 45年前のユーザが、S式を好み、また、それが言語をより強力にするのだということをみつけていたということでもある

2007-12-16

Practical Common Lisp (8)

| 21:30 | Practical Common Lisp (8) - わだばLisperになる を含むブックマーク はてなブックマーク - Practical Common Lisp (8) - わだばLisperになる

引き続きPractical Common Lisp 第三章3. Practical: A Simple Databaseを読んでみます。

Removing Duplication and Winning Big

この段落は、殆ど文章で、マクロの解説のようなので、文章を纏めてメモすることにします。

  • データの登録、更新、削除、保存、読み出しと一通り作成できた。大体50行位にまとまった。
  • しかし、これまでのコードにはコードの重複があって、ちょっと気になる。
  • 具体的には、whereの中で、(if title (equal (getf cd :title) title) t)の様にif式がずらっと並んでるのが気になる。updateもそんなところがある。
  • コードの重複で良くないところは、コードを追加/修正する場合、散らばった個所を一つずつ修正しなくてはいけないところ。
  • whereについて言えば、予め決めたキーワードを設定し、本体にも相応するキーワードを埋め込んでいるが、これも修正時には手間。
  • whereの実行時に動的に展開することによって解決できないか。
(select (where :title "Give Us a Break" :ripped t))

という表現が、実行時に

(select
 #'(lambda (cd)
     (and (equal (getf cd :title) "Give Us a Break")
          (equal (getf cd :ripped) t))))

と変換されるとすれば可能。

  • ここでマクロ登場
  • Common Lispのマクロは、C等の文字列置換がベースのプリプロセッサとは違い、コンパイラがコンパイル時にマクロが書かれた場所に展開してからLispの式として評価する。
  • 関数とマクロは全く違った効果を持つもの
  • 簡単なマクロの例
(defmacro backwards (expr) (reverse expr))

CL-USER> (backwards ("hello, world" t format))
hello, world
NIL
  • 動作の説明
    • ("hello, world" t format)という引数で、マクロの本体を評価する。マクロの引数は評価されない:(reverse '("hello, world" t format))のような感じ
    • '(format t "hello, world")が展開された式となる
    • 展開された式をもとにマクロがあった文脈でREPLが実行
  • 謂わば、上で定義したbackwardsは、引っくり返ったLisp式を実行する新しいLisp言語を定義したようなもの。そして、それは、通常の式に展開されるので、普通に書いたコードと効果は何等変わらず、まったく同じにコンパイルされる。
  • それで、問題であったwhareにはこのことがどう使えるか→キーワード毎に(equal (getf cd field) value)というような式が生成され、それが実行されるようになれば良い。
(defun make-comparison-expr (field value)    ; 駄目
  (list equal (list getf cd field) value))

ではどうか。

  • equalや、getf、cd、value等全部評価されることになる。fieldとvaluesは評価されても良いが、equalや、getf、cdは評価されては困る→quote(')を使う。
(defun make-comparison-expr (field value)
  (list 'equal (list 'getf 'cd field) value))
  • 式は生成できるようになったが、もっと読み書きしやすい方法がある。式の評価する部分としない部分をわかりやすく書くことができ、それには、バッククウォート(`)とコンマ(,)を組合せて使う。
`(1 2 (- 1 2))        ==> (1 2 (- 1 2))
`(1 2 ,(- 1 2))       ==> (1 2 3)

という風に書ける。

バッククオートを使うと、make-comparison-exprは

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

とわかりやすく書ける。

  • 以前に定義したwhereを振り返ると、キーワードを判定する式がANDで纏められていた。とりあえず、複数のキーワードを処理する節を纏めてリストにして返す関数は、
(defun make-comparisons-list (fields)
  (loop while fields
     collecting (make-comparison-expr (pop fields) (pop fields))))

と書ける。

  • 新しくLOOPがでてきたが22章で詳しく解説する。また、POPはPUSHの反対の作用をする関数。これらを纏めて新しいwhereをマクロで定義できる
(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)
  • 引数部の&restパラメータの説明
    • &keyに似ているが、&restは複数の引数を纏めて一つのリストとして扱えるようにする。
(where :title "Give Us a Break" :ripped t)

という引数が与えられた場合、変数clauseの中身は

(:title "Give Us a Break" :ripped t)

となる。

  • 定義したマクロがどのように展開されるかを確認するには、MACROEXPAND-1を使う
CL-USER> (macroexpand-1 '(where :title "Give Us a Break" :ripped t))
#'(LAMBDA (CD)
    (AND (EQUAL (GETF CD :TITLE) "Give Us a Break")
         (EQUAL (GETF CD :RIPPED) T)))
T

Wrapping Up

  • ということで、コードの重複を除去することに成功したが、同時に、よりコードを一般化できたことに気付く
  • マクロというのは、抽象化の一つで、構文上の抽象化といえる
  • 今のところは、make-cd、 prompt-for-cd、add-cdといった関数と一緒に使っているだけだが、whereマクロ自体は、plistベースのデータベース一般で使うことができるものになった。
  • 作成したデータベースは、完全なものには程遠いが、色々な機能の追加をあれこれ考えてみることができるだろう。複数のテーブルを扱ったり、クエリーの方法に凝ってみる等。27章で作るMP3データベースでそういう機能を実装してみたい。

Practical Common Lisp (7)

| 18:06 | Practical Common Lisp (7) - わだばLisperになる を含むブックマーク はてなブックマーク - Practical Common Lisp (7) - わだばLisperになる

Updating Existing Records--Another Use for WHERE

引き続きPractical Common Lisp 第三章3. Practical: A Simple Databaseを読んでみています。
とりあえず出てきたコードを暗記して再現して感想をメモ
(defun update (selector-fn &key artist title rating (ripped nil ripped-p))
  (setf *db*
	(mapcar (lambda (row)
		  (when (funcall selector-fn row)
		    (if artist (setf (getf row :artist) artist))
		    (if title (setf (getf row :title) title))
		    (if rating (setf (getf row :rating) rating))
		    (if ripped-p (setf (getf row :ripped) ripped)))
		  row)
		*db*)))
できた。今度はデータの内容を更新するものを作成する様子。
(defun delete-row (selector-fn)
  (setf *db* (remove-if selector-fn *db*)))
できた。データを削除するものらしい。
以上を踏まえて本文を読んでみる
  1. データベースにはデータを更新する機能が必須なので作る。
  2. これも、SQLに倣ってupdateという名前で作成することにして、データの抽出には定義したwhereを使用する。
  3. MAPCARの説明
    • リストの中の一つ一つのアイテムを総嘗めで処理するのには、mapcarが使える。
  4. SETFの説明。
    • (setf (getf row :ripped) ripped)のような形式が初めてでてきた。ちょっとややこしいが、詳細は、6章で説明する。とりあえず、setfは、変数の名前だけでなく、「変数が格納されている場所」を指定することにより、その場所に値を格納することができるとでも思っておく。
  5. REMOVE-IF/REMOVE-IF-NOTの説明
    • REMOVE-ifは条件を判定する関数とリストを引数に取り、リストのアイテムを一つずつ調べ、条件が満されたアイテムが含まれないリストを返す。REMOVE-IF-NOTは、条件を満さないもののリストを返す。つまり、リストから不要なものを取り除いた結果を返す。

2007-12-14

Practical Common Lisp (6)

| 17:35 | Practical Common Lisp (6) - わだばLisperになる を含むブックマーク はてなブックマーク - Practical Common Lisp (6) - わだばLisperになる

引き続きPractical Common Lisp 第三章3. Practical: A Simple Databaseを読んでみています。一日1段落のペースになりつつあります…。

Querying the Database

とりあえず出てきたコードを暗記して再現して感想をメモ
(select :artist "Dixie Chicks")

とりあえず、こういう関数を作って検索することにしたのだろう。

(defun select-by-artist (artist)
  (remove-if-not (lambda (cd) (equal (getf cd :artist) artist))
		 *db*))

できた。アーティスト名を引数にとって検索するものの様子。

Practical Common Lispでは、lambdaにはfunction(#')を付ける派のようです。

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

できた。これはなんだろう高階関数化するんだろうか。

(defun artist-selector (artist)
  (lambda (cd) (equal (getf cd :artist) artist)))

無名関数に名前を付けるらしい。

(select (artist-selector "Aldo Ciccolini"))

のように使うらしい。でもなんとなく無理矢理な気が。

(defun where (&key artist title rating (ripped nil ripped-p))
  (lambda (cd)
    (or 	
     (if artist (equal (getf cd :artist) artist))
     (if title (equal (getf cd :title) title))
     (if rating (equal (getf cd :rating) rating))
     (if ripped-p (equal (getf cd :ripped) ripped)))))

間違えた。動きは同じだけど、andと、デフォルト値tの組み合わせで

(defun where (&key artist title rating (ripped nil ripped-p))
  (lambda (cd)
    (and
     (if artist (equal (getf cd :artist) artist) t)
     (if title (equal (getf cd :title) title) t)
     (if rating (equal (getf cd :rating) rating) t)
     (if ripped-p (equal (getf cd :ripped) ripped) t))))

だった。

自分には、

(or (if pred t nil)
    (if pred t nil))

の方が

(and (if pred nil t)
     (if pred nil t))

より自然に感じるな。

とりあえず、一般化されたwhereという関数を作って、selectに渡すらしい。関数型言語っぽい。

以上を踏まえて本文を読んでみる

(select :artist "Dixie Chicks")という感じで検索できるようにしたい。

  • REMOVE-IF-NOTの説明
    • リストから条件に合わないものを削除して、結果のリストを返す。結果として欲しいもののリストが返る。
    • REMOVE-IF-NOTには関数を渡す。LAMBDAで作った無名関数も渡せる。
  • LAMBDAの説明。
    • 無名関数を作る。名前がないことを除いては、DEFUNの様なもの。
  • 以上を踏まえて、select-by-artistを作ってみる。
  • 同じ流れでselect-by-title, select-by-rating, select-by-title-and-artistと作っても良いが、もう少し一般化して、selectという関数を引数に取るより一般化された関数を作ってみる。
  • selectの中の変数名に#'は付けなくて良いのかという疑問→いりません
  • selectに渡す関数をもっと一般化してみる
    • キーワードパラメータの説明
(defun foo (&key a b c) (list a b c))

(foo :a 1 :b 2 :c 3)  ==> (1 2 3)
(foo :c 3 :b 2 :a 1)  ==> (1 2 3)
(foo :a 1 :c 3)       ==> (1 NIL 3)
(foo)                 ==> (NIL NIL NIL)

という風に、キーワードと値のペアを便利に渡す仕組みがCommon Lispにはあるので、これを利用する。

      • 特徴
        • キーワード:値というペアで値が特定できるので、関数呼び出しの時に、引数のペアの順番は任意にできる。
        • 関数呼び出しの時に、引数のペアを与えなければ、定義時に設定したデフォルト値が渡される
        • デフォルト値設定について
(defun foo (&key a (b 20) (c 30 c-p)) (list a b c c-p))

という風に指定できる。

aは指定していないが、指定していない場合、デフォルト値としてNILが設定される。

bは、デフォルト値は20

cは、デフォルト値は30で、更に、値が指定されているかどうかを判定する機構も使っている。

この場合、c-pという変数に、tかnilが渡る。

  • 以上を踏まえて、SQLっぽく、whereという関数を定義することによって、
(select (where :artist "Dixie Chicks"))

という風に使えるようにしてみる。

  • where関数の定義と説明
    • ifの中で、ripped-pを判定しているのは、
(if ripped (equal (getf cd :ripped) ripped) t)

では、rippedの真偽を確かめたいのか、それとも引数の指定がないのでデフォルト値でnilが渡ってきたのが区別できないため。ripped-pで調べる必要がある。

2007-12-13

CLOSチュートリアル (7)

| 17:26 | CLOSチュートリアル (7) - わだばLisperになる を含むブックマーク はてなブックマーク - CLOSチュートリアル (7) - わだばLisperになる

Common Lisp クックブックさんのところのCLOSのチュートリアルに新しい練習問題(CLOSチュートリアル - 4.6. 修飾子とメソッド結合)が出たので挑戦!。

問題.1

initialize-instance に :after メソッドを追加し、すべての aardvark をイギリスのケンブリッジ(Cambridge, England)から来たことにします。もう一つ、次の関係が成り立たなくなるようなメソッド(どんな修飾子をつけますか?)を追加しなさい。

解答.1

とりあえず、initialize-instanceと:afterの組み合わせで作成してみました。

(defmethod initialize-instance :after ((aardvark aardvark) &rest init-args)
  (declare (ignore init-args))
  (setf (slot-value aardvark 'comes-from) "Cambridge, England"))

animalクラスでcomes-fromは:readに指定されているので、(setf (comes-from aardvark) "Cambridge, England")はできないので、スロット変数を直に指定してちょっと強引に書き換え。これで良いんだろうか。もっと良い方法があるような…。

:accessorだったなら

(defmethod initialize-instance :after ((aardvark aardvark) &rest init-args)
  (declare (ignore init-args))
  (setf (comes-from aardvark) "Cambridge, England"))

でいけます。

;; 動作
(setf zot (make-instance 'aardvark :legs 4 :comes-from "Brittany"))

(comes-from zot)
;=> "Cambridge, England"

それで次は

(make-instance 'cannibal :diet (make-instance 'cannibal))

が成立しないようなメソッドということですが、とりあえず、cannibalクラスを定義して、

(defclass cannibal (mammal) ())

(defmethod initialize-instance :before ((cannibal cannibal) &rest init-args)
  (when (eq (class-of cannibal) (class-of (getf init-args :diet)))
    (error "Cannibal!, Cannibal!")))

と:beforeを使ってdiet引数に同じクラスが渡された場合にerrorになるようにメソッドを追加してみました。しかし、これで良いんだろうか。

;; 動作
(setq cannibal (make-instance 'cannibal :diet (make-instance 'cannibal)))
;=> エラー "Cannibal, Cannibal"

(setq cannibal (make-instance 'cannibal :diet "foo"))
;=> #<CANNIBAL #x30004393E88D>

問題.2

initialize-instance は、他のオブジェクトシステムのコンストラクタを強化したものと見ることもできます。ただし、CLOSにはデストラクタがありません。デストラクタがないのは問題ですか?

解答.2

まず、デストラクタがなんだか良く分からないので、とりあえず、Wikipediaで調べてみたところ、コンストラクタの逆で後始末をするものということで、「Javaではデストラクタは存在せず、近い機能に自動ガベージコレクションによって機能するファイナライザがある。」ってことなので、Common LispにもGCあることだし、GC任せってことでしょうか。

Practical Common Lisp (5)

| 13:58 | Practical Common Lisp (5) - わだばLisperになる を含むブックマーク はてなブックマーク - Practical Common Lisp (5) - わだばLisperになる

引き続きPractical Common Lisp 3. Practical: A Simple Databaseを読んでみています。どんどんペースが落ちてきました…。

Saving and Loading the Database

とりあえず出てきたコードを写経(暗記して再現)して感想をメモ
(defun save-db (filename)
  (with-open-file (output filename
			  :direction :output
			  :if-exists :supersede)
    (with-standard-io-syntax 
      (print *db* output))))

できた。これで*db*の内容をファイルに書き出すんだろう。

with-standard-io-syntaxってのは初めて使った。

CLHSで確認してみたところ、これに囲まれた入出力は標準化される(CLHSに一覧あり)ので、処理系の違いを気にする必要がないとのこと。

出力されたデータは、処理系に依存しないものにしたいので、こうしているのだろう。

  • with-standard-io-syntaxを試してみる。
(let ((*print-case* :downcase))
  (print 'foo))
;=>fooと印字される

(let ((*print-case* :downcase))
  (with-standard-io-syntax
    (print 'foo)))
;=>FOOと印字される(標準では:upcaseなため)
(defun load-db (filename)
  (with-open-file (in filename :direction :input)
    (with-standard-io-syntax
      (setf *db* (read in)))))

できたけど、:direction :inputは不要だった。デフォルトではinputになるからだろう。

以上を踏まえて本文を読んでみる。
  • save-db
    • WITH-OPEN-FILEの解説
      • ファイルを開いてストリームと結びつけ後始末もしてくれるマクロ。キーワード引数が色々取れて、入出力の向き、ファイルが存在しない場合の動作等、色々指定可能。
    • PRINTの説明。
      • FORMATと違って、READで読み込める形式で出力することになっている。
    • WITH-STANDARD-IO-SYNTAXの説明。
      • 写経時の考察で大体合っていて、可搬性を高めるためのに使用している。
  • load-db
    • WITH-OPEN-FILEはデフォルトでは:inputを指定した動作となるので省略可能。
    • SETFの説明。
      • *db*の内容を上書きしている。

2007-12-11

Practical Common Lisp (4)

| 21:54 | Practical Common Lisp (4) - わだばLisperになる を含むブックマーク はてなブックマーク - Practical Common Lisp (4) - わだばLisperになる

引き続きPractical Common Lisp 第三章3. Practical: A Simple Databaseを読んでみています。

Improving the User Interaction

とりあえず出てきたコードを写経して感想をメモ
(defun prompt-read (prompt)
  (format *query-io* "~A:" prompt)
  (force-output *query-io*)
  (read-line *query-io*))

できた。

*query-io*は初めて使った。

promptに好きな文字列を指定できるらしい。

多分、これでユーザからの問い合わせるのだろう。

(defun prompt-for-cd ()
  (make-cd
   (prompt-read "Title")
   (prompt-read "Artist")
   (prompt-read "Rating")
   (prompt-read "Ripped [Y/N]")))

できた、make-cdの対話的なバージョンの様子。

でもこれだと、rippedがy/nの入力ってことになるが。後で内部的にt/nilに変更するのだろうか。

(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-or-n-pを使うことにしたらしい。これだと、y/nを、t/nilに変換できる。

それと、レイティングの数字は、そのままだと、文字列で取得することになるので、parse-integerでパーズしているのだろう。

それで、:junk-allowedをtにすることにより、不正な入力の場合でもエラーにしないで、nilを返すようにして、orでくるんで、デフォルト値を設定している様子。

しかし、y-or-n-pは、処理系によって微妙に動作が違うみたい。Clozure CL(OpenMCL)だと、前後に改行が入ったりする(printの動作に似た感じ)SBCLならそういうことはない。

(defun add-cds ()
  (loop (add-record (prompt-for-cd))
        (if (not (y-or-n-p "Another?")) (return))))

add-recordを対話的にするためのラッパーの様子。

本文を読む
  • *QUERY-IO*を使った問い合わせ方法の説明。
    • FORCE-OUTPUTの実行が必要な処理系もある。
  • READ-LINE関数の説明。
    • 取得する文字列に改行文字は含まれない。
  • PARSE-INTEGEの説明。
    • 不正な値の入力はデフォルトだとエラーになるが、:JUNK-ALLOWEDでTを指定すると、その場合でもNILを返すという挙動になる。
  • ORによるデフォルト値の設定
    • Perl等の、||や、&&演算子と同じ感覚で、and、orが使える。
  • Y-OR-N-Pの説明。
    • ユーザのy/nの入力をt/nilで返す。
  • LOOPの説明。
    • 簡単な無限ループとしての使い方の説明。脱出には、RETURNを使う。

次回、Saving and Loading the Databaseより再開。

CLOSチュートリアル (6)

| 15:18 | CLOSチュートリアル (6) - わだばLisperになる を含むブックマーク はてなブックマーク - CLOSチュートリアル (6) - わだばLisperになる

Common Lisp クックブックさんのところのCLOSのチュートリアルに新しい練習問題(CLOSチュートリアル - 4.4. すべてはオブジェクトの中にあるCLOSチュートリアル - 4.5. その他の特定子(まだCLOSオブジェクトは不要です))が出たので挑戦!。

4.4 問題.1

Lispの describe 関数は、処理中に describe-object 総称関数を呼び出します。各処理系は各クラスにこのメソッドを実装する必要があり(ただし、standard-object 以外のクラスへの実装は任意です。また、ユーザは自由にメソッドを追加して構いません)、このメソッドがオブジェクトとストリームの二つの引数を受け取るように実装しています。実装者が describe-object のすべてのメソッドを一つのファイルに書くべきか、複数のファイルに分散して書くべきか議論しなさい(例えば、aardvark で特定化されるメソッドを、クラス定義と他のメソッドと一緒に "aardvark.lisp" ファイルに書くべきかどうか)。で、どんな意見が出ましたか?

4.4 解答.1

他のオジェクト指向言語を触ったことがないので、普通がどういうものなのか良く分からないのですが、分けて書けてしまうということは、分けて書いた方が分かりやすいような使い方もできてしまうということかもしれず、別に全部一つに纏める必要はないんじゃないかと思う。

4.4 問題.2

二番目以降の複数の引数でメソッドを特定化しなければならない例をいくつか考えなさい。

4.4 解答.2

(defmethod concat ((str string) (num number))
  (concatenate 'string str (princ-to-string num)))

(defmethod concat ((num number) (str string))
  (concatenate 'string (princ-to-string num) str))

(defmethod concat ((n1 number) (n2 number))
  (concatenate 'string (princ-to-string n1) (princ-to-string n2)))

(defmethod concat ((s1 string) (s2 string))
  (concatenate 'string s1 s2))

とかどうだろう。ちょっと無理矢理な気もするけれど…。本当は可変長の引数が取れるようにしてみたかったけど、defmethodと&restの組み合わせの定番処理方法がいまいち良く分からず…。

4.5 問題.1

リストのための my-describe メソッドを書きなさい。

4.5 解答.1

(defmethod my-describe ((list list))
  (format t "~S is a list object~:[~;and a null object~]." list (null list)))

listは、nil+consなので、nilの場合に追加メッセージを表示するようにしてみた。

4.5 問題.2

antelope クラスのインスタンス Eric のための print-object メソッドを書きなさい。Eric のクラスを変更しても、そのメソッドはまだ適用可能だと思いますか?

4.5 解答.2

(defmethod print-object ((antelope antelope) stream)
  (print-unreadable-object (antelope stream :type 'T :identity 'T)
    (princ "[Hello, Antelope!]")))

Ericのクラスを変更してメソッドの挙動を確認

(class-of Eric)
;=> #<STANDARD-CLASS ANTELOPE>

(print-object eric t)
;=> #<ANTELOPE [Hello, Antelope!] #x300041D3065D>

(change-class eric 'aardvark)
;#<STANDARD-CLASS AARDVARK>

(print-object eric t)
;=> #<AARDVARK #x300041D3065D>

ということで、クラス変更によりantelopeの特定化からは外れた様子。

2007-12-08

CLOSチュートリアル (5)

| 06:30 | CLOSチュートリアル (5) - わだばLisperになる を含むブックマーク はてなブックマーク - CLOSチュートリアル (5) - わだばLisperになる

Common Lisp クックブックさんのところのCLOSのチュートリアルに新しい練習問題(CLOSチュートリアル - 4.3. 総称関数と次メソッド)が出たので挑戦!。

問題.1

あなたが使用しているLispの処理系の、class-precedence-list の総称関数を調べなさい。

解答.1

OpenMCL 1.1の場合で確認したところ、class-precedence-listは、総称関数として定義されているものの、cclパッケージの%inited-class-cplを呼び出している定義が一つ。

その%inited-class-cplは、%class-cplが一覧をみつけられたならそれを返し、見付からない場合、ccl:*cpl-classes*一覧に問い合わせのクラスが存在する場合、ccl:compute-cplでリストを計算、存在しない場合、動的にアップデートをかけてその後再度計算。

…という感じですが、こんな答じゃ駄目だろうなと。各処理系のclass-precedence-listの簡単なバージョンを実装してみせるのが一番良い解答なのかなという気がしないでもないのですが、実力的に無理なので、できるようになったら再挑戦したいところ。

問題.2

(comes-from Eric) を評価したとき、スロットのリーダ comes-from はどのクラスから継承されているでしょうか?そのメソッドをオーバーライドし、 antelope が常にアフリカから来ることにしなさい(現実的ではありませんが)。

解答.2

とりあえず、comes-fromはどこからかというのは、

(generic-function-methods #'comes-from)
;=>(#<STANDARD-READER-METHOD COMES-FROM (ANIMAL)>) 

で、animalから来てると思います。

それで、オーバーライドですが、簡単に、

(defmethod comes-from ((antelope antelope))
  "Africa")

という定義で

(comes-from Eric)
;=> Brittany

;; オーバーライドする
(defmethod comes-from ((antelope antelope))
  "Africa")

(comes-from (make-instance 'animal :comes-from "foo"))
;=>foo
(comes-from (make-instance 'mammal :comes-from "foo"))
;=>foo
(comes-from (make-instance 'antelope :comes-from "foo"))
;=> Africa
(comes-from Eric)
;=> Africa

というようにantelopeを継承するものは、"Africa"を返すようになります。

問題.3

call-next-method の無限エクステントの効果を試してみなさい。

解答.3

これはなかなかどの辺が、無限エクステントなのか理解できていないのですが、

(defclass one () ())
(defclass two (one) ())
(defclass three (two) ())

というクラスを定義して

(defmethod baz (obj)
  (print "end"))

(defmethod baz ((one one))
  (print "1"))

(defmethod baz ((two two))
  (print "2"))

(defmethod baz ((three three))
  (print "3")
  #'call-next-method)

というメソッドを定義します。

それで、threeのインスタンスを作成するんですが、threeは、返り値として自身のcall-next-methodを返すので、それを変数に保存します。

(setq baz (baz (make-instance 'three)))
;-> 3

それで、保存した変数にfuncallを適用すると

(funcall baz)
;-> 2

となり、上位メソッドが呼ばれていることが確認できます。

それで、ここにきてthreeクラスの内容を変更して、oneが直接のスーパークラスということに変更します。

(defclass three (one) ())

実行結果を確認してみます。

(funcall (baz (make-instance 'three)))
;-> 3
;-> 1

threeの次にoneのメソッドが呼ばれています。

ここで、さっき保存したものを再度呼び出してみます。

(funcall baz)
;-> 2

という風にもとのままです。

…うーん、これで確かめたことになるんでしょうか。

call-next-method自体が、次のメソッドが何であるかを静的に保存しているってのは確認できてる気はするんですが…。

ちなみに、メソッド自体を消しても

(fmakunbound 'baz)

(baz 'two)
;-> error

(funcall baz)
;->2

呼びだせます。

2007-12-06

Practical Common Lisp (3)

| 01:21 | Practical Common Lisp (3) - わだばLisperになる を含むブックマーク はてなブックマーク - Practical Common Lisp (3) - わだばLisperになる

引き続きPractical Common Lispをさらってみています。

3. Practical: A Simple Database

この章では、簡単なCDのデータベースを作成してみる。

この本の方針とか説明色々。

CDs and Records

CDのアーティスト名、アルバム名、お気に入り度、mp3にリッピングしたかどうか、の4つを格納する。

4つのデータのまとまりを扱うということであれば、Common Lispには沢山の表現方法がある。リストで表現するとか、CLOSを使うとか。

  • とりあえず例示されているコードを写経してみる
(list 1 2 3)

とりあえず、listを使うことにしたのだろう。

(list :a 1 :b 2 :c 3)

plistだろうか。

(getf (list :a 1 :b 2 :c 3) :c)

plistを軸に4つのデータを管理することにしたみたい。

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

間違った。albumじゃなくて、titleだった。アルバムのデータのplistを作る関数の様子。

  • 文章を読んでみてメモする

plistについての解説。

普通のリストだけれど、キー、値、が交互に並べた形式をproperty listとかplistと呼ぶ。

":"から始まってるシンボルの意味の説明。

キーワードシンボルと呼ばれる。

plistを作るのには、普通のlist関数を使う。

plistを操作するのには、getfが使える。

(getf plist key)

で、keyと対の値が取り出せる。

値のセーブについては後で。

ということで、CDのデータをplistにする、make-cd関数を作成。

Filing CDs
  • 例示されているコードを写経してみる
(devar *db*)

とりあえず、DBには、大域変数の*db*を定義してそれを使うことにしたらしい。

(defun add-record (cd) (push cd *db*))

大域変数*db*にの結果をどんどんプッシュすることにしたらしく、その為のadd-record関数。

  • 文章を読んでみてメモする

読んでみる。大体予想したことと同じだった。

Looking at the Database Contents
  • 例示されているコードを写経してみる
(defun dump-db ()
  (dolist (cd *db*)
    (format t "~{~A:~10T~A~%~}~%" cd)))

*db*の内容はそのままでは、plistの羅列なので、見易くプリントしてくれる関数らしい。

フォーマット指示子の~Aとか、~Tとかはこの本では小文字にするスタイルらしい。ちなみに、Norvig & Pitmanの

Tutorial on Good Lisp Programming Style(PostScriptファイル)(okshiraiさん訳)では区別しやすいように大文字にすることを推奨。

(defun dump-db ()
  (format t "~{~{~A:~10T~A~%~}~%~}" *db*))

さらにformatで凝ったことをして、dolistのループを不要にした様子。

  • 文章を読んでみてメモする

*db*の内容を見易く表示する関数を作ろう。

dolistの説明。リストの内容を一つずつ処理させたいときに使う。

formatの説明。謎のフォーマット文字列。Cのprintfとか、Perlとかに比べてもそんなに複雑ではない筈。

formatは2つの引数を取って、最初のtは、*standard-output*を指定するという意味。

formatの書式指定子は、~から始まる。printfだと、% 。

~a指示子は、aesthetic(美的)な指示子。(語呂合せで覚えようというのだろうか。)

人が読み易い形で表示してくれる。

引数に文字列を与えると、ダブルクオートを取り除いて表示する。

キーワードシンボルを与えると、:を取り除いて表示する。

ふと思って確認してみた。
:fooというのは、keyword:fooの略記になるわけですが、こういうパッケージを含んだ記法を与えるとどうなるのか、
結果:
(format t "~A" keyword::foo)
(format t "~A" keyword::foo)
(format t "~A" 'keyword::foo)
(format t "~A" 'keyword:foo)
(format t "~A" 'cl-user::foo)
=>FOO
全部FOOでした。

~tというのは、表の整形に使うもので、~10tは、10カラム目からスタートするということ。その前は空白で埋める。

~{と~}の組み合わせは、引数にリストを取り、リストの中身を取り出して~{と~}の間に記述されたパターンにマッチさせながら繰り返し表示する。

~%は改行。

ということで、dolistも省略できたりする。

長いので次回Improving the User Interactionより再開。

2007-12-05

CLOSチュートリアル (4)

| 15:48 | CLOSチュートリアル (4) - わだばLisperになる を含むブックマーク はてなブックマーク - CLOSチュートリアル (4) - わだばLisperになる

Common Lisp クックブックさんのところのCLOSのチュートリアルに新しい練習問題(CLOSチュートリアル - 4.2. defmethod マクロ)が出たので挑戦!。

問題.1

aardvark と antelope の表示を、デフォルトのものよりもわかりやすくしなさい。すべてのCLOSオブジェクトは print-object メソッドによって表示されます。このメソッドの引数は (オブジェクト ストリーム) です。また、デフォルトのメソッド(standard-object を表示する)がどう定義されているのか考えなさい。

解答.1

ちょっと飾りをつけるということで、aardvarkは、継承関係の一覧を追加で表示し、antelopeはスロットの一覧を追加で表示させることにしてみました。スロットの一覧取得には、Meta!の、moptilitiesパッケージを使ってみています。とりあえず、どっちも総称関数にしてクラスを特定できない場合は、print-objectに処理を丸投げしてます。

(require :moptilities)

(defmethod print-aardvark-object ((obj aardvark) stream)
  (print-unreadable-object (obj stream :type 'T :identity 'T)
    (format stream "(~{~A~^ -> ~})" (mapcar #'class-name 
					  (#+sbcl sb-mop:class-precedence-list
					   #+openmcl ccl:class-precedence-list
					   (class-of obj))))))

(defmethod print-aardvark-object (obj stream)
  (print-object obj stream))

;; 動作
;;==> #<AARDVARK (AARDVARK -> MAMMAL -> ANIMAL -> STANDARD-OBJECT -> T) #x8B7F466>

(defmethod print-antelope-object ((obj antelope) stream)
  (print-unreadable-object (obj stream :type 'T :identity 'T)
    (format stream "[SLOTS:~{~A~^ ~}]" (moptilities:slot-names obj))))

(defmethod print-antelope-object (obj stream)
  (print-object obj stream))

;; 動作
;; ==> #<ANTELOPE [slots:LEGS COMES-FROM DIET] #x8BFF466>

また、print-objectは、総称関数で色々混み入った定義のようですが、簡単に定義するなら

(defmethod my-print-object (obj stream)
  (print-unreadable-object (obj stream :type 'T :identity 'T)))

のように定義できると思います。

問題.2

次のメソッド定義を見て、コンパイラが不要な処理を無視できる可能性についてできるだけ詳しくまとめなさい。(この問題を作るのに Steve Haflich の助けを借りました。)

解答.2

これはちょっと難しくて良く分からないけれど、とりあえず、自分が考えられる範囲で。

1. このメソッドでは、引数bazはfrobクラスと特定されていることになっているため、(mangle)がbaz値のクラスを変更するようなことをしないことを証明でき、かつ、barの結果が、frobクラスに特定されることが証明できれば、etypecaseの判定は無くすことができると思う。

(defmethod foo ((baz frob))
  (loop :initially (mangle)
        :while baz do (setf baz (bar baz))))
さらに、その場合bazがnilになることはない(frobクラスがnilになることはない)ので、
(defmethod foo ((baz frob))
  (loop :initially (mangle)
        :do (setf baz (bar baz))))
にできるんじゃないだろうか。

2. (mangle)が、bazをnilにしてしまう場合。

(defmethod foo ((baz frob))
  (mangle)
  nil)

にできると思う。

…しかし全然この解答には自信がないです。

2007-12-04

Practical Common Lisp (2)

| 03:42 | Practical Common Lisp (2) - わだばLisperになる を含むブックマーク はてなブックマーク - Practical Common Lisp (2) - わだばLisperになる

ひき続きPractical Common Lispを読んでみます。

2. Lather, Rinse, Repeat: A Tour of the REPL

Common Lispの開発環境についての説明。

Matthew Danish氏とMikel Evins氏が作っている「Lisp in a Box」について。

「Lisp in a Box」というのは、Emacsエディタと、その上でCommon Lisp処理系と連動して動作するSLIMEという強力な開発環境をセットにしたもの。

このセットはCommon Lispの開発スタイルの特徴である、高度に対話的で、インクリメンタルな開発というのを十分に洗練された方法で提供してくれる。

SLIMEはどのOSや、ベースとなるCommon Lispの処理系を選んでも、インターフェースは統一されているので、その違いをあまり意識することなく使うことができる。

商用的なベンダーが提供しているGUIのIDEもあるけど、Common Lispの基本的なことを修得する、という目的には、ちょっとややこしい嫌いがあるので、「Lisp in a Box」がお勧め。

Choosing a Lisp Implementation

とりあえず、ベースとなるCommon Lispの処理系を選ぶ。

処理系の選択というと、Perl, Python, Visual Basic (VB), C#, Javaに馴染み深い人からすると、ちょっと不思議に思うかもしれない。

Common LispはANSIで規格が策定されていて、ANSI Common Lispといえば、それに則って開発された処理系のことになる。

特定のベンダーが提供する処理系が言語の標準実装だったり、特定の実装が唯一の処理系だったりするのとはちょっと違う。

というわけで、ANSI規格に則ったプログラムかを確認していれば他の処理系でも動く。

でも、その一方で、プログラムで処理したいことが全部の規格の範疇に納まってるとは限らない。

そういうプログラムでもできるだけ、移植性の高いものにしたい。

Common Lispでは、そういう処理系の差異を吸収するような柔軟な仕組みもあるので、後の章でそれを解説する。

さて、処理系の選択だけど、メインで使ってるOSが何かってことに一番左右されるんじゃないだろうか。

商用のANSI Common Lisp処理系であるAllegro Common Lispが使いたいって場合には、Linux, Windows, とOS X用が使える。

この本の内容を実行するということであれば試用版Allegro Common Lispが使える。

オープンソースの処理系が良い、ということであれば、選択肢はいくつかあって、SBCLという性能の良い処理系が使える。

SBCLは、CMUのCMU Common Lispをベースに開発されたもの。

CMU Common Lispそれ自体も良い選択だと思う。けれど、SBCLがよりインストールが簡単で、21-bitユニコードをサポートしている。

Mac OSXを使ってるということであれば、OpenMCLが良い選択だと思う。コンパイラはネイティブなマシンコードを出力するし、スレッドもサポートしている。おまけに、Carbonや、Cocoaのインターフェイスも整備されている。OpenMCLには、商用、オープンソースの両方がある。

この本のCommon Lispのコードは、どの処理系を選んでも問題なく動く筈のものを載せている。そうじゃない場合は断り書きを書く。

また、SLIMEは、処理系の違いを吸収してくれる筈。

Common Lispの処理系の出力は、GNU/Linuxで動く、Allegroのものを使った。エラー表示や、デバッガへ入った表示だけど、これは処理系によって結構違う表示になることがある。

Getting Up and Running with Lisp in a Box

始めてのLisp in a Boxのセットアップを最小限の手間で完了させるには、自分が選択したOSやそのOSで使える処理系を良く吟味する必要がある。

これらの選択については、32章で解説している。

Lisp in a Boxでは、Emacsをメインのエディタとして使うが、ちょっとしたEmacsの使い方を知っておく必要がある。

〜Emacsの基本的な使い方についての説明が続く〜

SLIME:C-c C-z
 REPLバッファにカーソルを移動する。

Free Your Mind: Interactive Programming

さて、Lisp in a Boxを起動して最初に目にするものは、

CL-USER>

というプロンプト。

これは、DOSとか、Unixシェルのプロンプトと同じで、入力を受けつけていて、入力→実行というサイクルを繰り返す。

read-eval-print loopとか、略してREPLと呼ばれる。

top-levelとかthe top-level listenerとかthe Lisp listenerとも呼ばれる。

このREPLに式を読み込ませて実行していくことになる。

実際とのところは、エディタ内に読み込まれたファイルの内容を対話的に評価する便利な仕組があるので、いちいちこのREPLにコピペして実行という訳ではない。

Experimenting in the REPL

REPLを使ってみる。まずは、"10"を入力して、Returnを押してみる。

CL-USER> 10
10

REPLという言葉に照らし合せるなら、

CL-USER> 10 ;READ(REPLのR)
10          ;EVAL-PRINT(EしてR)

ってことになる。

"10"はself-evaluatingオブジェクトといってそれ自身が既に結果となっているオブジェクト。

次に、(+ 2 3)を実行してみよう。

CL-USER> (+ 2 3)
5

括弧に囲まれたものは全部、リストと呼ばれる。

この場合は、+、2、3の3つがリストの内容。

リスト先頭は、特別扱いされて関数の名前という扱いになる。そしてそれ以降は、その関数の引数。

つまり引数2と3が、+という関数に渡されることになる。

しかし、こんなことより最初にやっておかないちゃいけないことがある、

"Hello, World," Lisp Style

プログラム解説書の掟"hello, world"

CL-USER> "hello, world"
"hello, world"

とするだけで、目的は達成できる。

"hello, world"は、ストリングでself-evaluatingオブジェクトなので、評価して"hello, world"が表示される。

もうちょっと普通のHello,Worldの例

format関数を使う。

CL-USER> (format t "hello, world")
hello, world
NIL

hello, worldと表示して、format関数の返り値である、NILが返っている。

NILは、Lispにおける真偽値の偽という意味。

もうちょっと進んで関数を作って表示させてみる。

関数の作成には、defunを使う。

CL-USER> (defun hello-world () (format t "hello, world"))
HELLO-WORLD

関数の名前で使える文字のうちでLisp的なところは、"-"が使えるところ。

hello_worldや、HelloWorldという名前より、hello-worldという名前が好まれる。

実行してみる。

CL-USER> (hello-world)
hello, world
NIL

hello, worldと表示してNILが返っている。

これは、関数の中で最後に評価された値が関数の返り値となるため。この場合はformat関数の返り値。

Saving Your Work

作った関数を保存するには->Emacsでファイルを.lispや、.clというファイルを作成してそれに書く。

SLIME: C-c C-q (slime-close-parens-at-point)

<<最新のCVS版SLIME(Lisp in a Boxではなく単体のもの)では代わりにslime-close-all-parens-in-sexpを使えとのこと。(slime-editing-commands.elの説明より)

SLIME: C-c C-] (slime-close-all-parens-in-sexp)

となっています。>>

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

エディタバッファのコンパイルしたい式の上で、C-c C-cを押すと式がコンパイルされる。

とりあえず、hello-world.lispという名前で関数の定義を保存。

一旦slimeを終了させて、再度、M-x slimeで起動。

(hello-world)を評価。

→エラー

ややこしい画面がREPLに表示される。これから抜けるには、"q"を押す。

hello-worldは定義されていないのでエラーになるのです。という訳で、保存したファイルを再度読み込ませる必要あり。

(load "hello-world.lisp")

で読み込ませる。

他の方法として、ファイルをコンパイルしてから、それを読み込ませるという方法もあり。

(load (compile-file "hello-world).lisp")

という感じになる。

上記2つの手順は、SLIMEでショートカットも定義されている

SLIME:C-c C-l (slime-load-file)
 指定したファイルをロード
SLIME:C-c C-k (slime-compile-and-load-file)
 指定したファイルをコンパイルしてからロード

まだまだ色々あるけど、この流れがLisp開発の基本。

REPLを使って新しいコードをロード、テスト、デバッグする。

本格的になると、終日同じ環境でプログラムを開発する人も結構いる。一旦立ち上げられた環境(イメージと呼ばれる)でプログラムは徐々に成長して行く。

REPL武勇伝:160万km彼方のNASAの宇宙探索機 Deep Space 1のバグを遠隔操作のREPLで対話的かつ動的に修正。

2007-12-03

Practical Common Lisp (1)

| 01:05 | Practical Common Lisp (1) - わだばLisperになる を含むブックマーク はてなブックマーク - Practical Common Lisp (1) - わだばLisperになる

Common Lispの優れた入門書として、評判のPractical Common Lisp

以前、著者のPeter Seibel氏が、Googleの検索キーワード「lisp tutrial」で首位を奪取したいキャンペーンを実施していた時には、このブログでもリンクを貼ってみましたが、めでたく、首位を奪取されたようです

お蔭様で何の関係もないこのブログが「lisp tutorial」で検索するとウェブ全体検索で4位、日本語のページ検索で1位になりました。(引っ越す前のブログ)

どういう仕組みで浮上してしまうのかは謎ですが、何でアクセスも非常に少ないこのサイトが浮上してしまうんでしょうか。

それはさておき日本語訳はまだ出版されておらず、読むとなると英語なので英語が苦手な自分はスルーしてましたが、スルーするのも勿体無いので、自分も読んでみることにしました。

ちなみに、現在、翻訳にTOP PAGEさんが取り組んでいて、10章まで作業は完了しているそうです。

出版されるとすると、本にするというのは何だかんだ大変そうなので、2009年春とかその位でしょうか。

これが出版されればOn Lisp以上にヒットし、Common Lispのユーザが増えて、その影響で日本語環境も整ってさらに快適で身近にCommon Lispでプログラムできるようになるに違いありません!

さて、読み進める方針ですが、英語が苦手ということもあり、コードがあったら、文章よりも先に例示されているコードを暗記して写経してみることにしました。

そのプロセスを通じて何が問題になるかがより明確になるんじゃないかと勝手に考えています。また、英語のハンデも若干補えるんじゃないかと思います。

では早速第一章。

文字だけの章なので、読みながらこのブログにメモとることにしました。以下非常に適当なメモなので、勉強する方は、原書を参照して下さい。

<<>>内は私のツッコミです。

1. Introduction: Why Lisp?

なんでCommon Lispなのかのまえに、著者がLispを使うようになるに至ったお話。

父ちゃんの話。

プログラマだった。

難航していたFortranを機軸にしたプロジェクトに関わった。

時は1980年初頭。AIブームの折、父ちゃんは困難の解決にLispが福音にならないかしらと思った。

Lispの本場の一つであるCMUに相談に行って色々訊いた。

Lispを使ってみることにした。

成功しちゃった。

おれ、息子の話。

そういう父ちゃんの話は知ってはいたが、高校/大学ともに、Lispを学ぶことはなかった。

<<とはいえ、プログラマにはなってるわけで結局似た者親子だな。>>

大学を出てからは、Perl->Java、C、C++という感じで仕事していた。

そんな折、生産性の高い言語について興味が湧いて来た。

というわけで、Lispはどうかと使い始めてみた。

そしたら予想外に生産性が高かった。

例えば…

昔Java初心者の頃に作ったことのある囲碁ゲームをLispで作ってみたが良い感じにすぐできた。

数年の経験を経たJavaで再実装するより生産性は高いと思った。

かつCommon Lispでは、全くの初心者であることを勘案するとグっと来た。

だからみんなもきっとそう感じるに違いない。

Why Lisp?

現場でメジャーに使われている言語との比較が色々。

Lispなら〜が〜よりできるという主張

<<Lisp好きな人が良く言う煽り文句が色々>>

「Lispはそれ自身をプログラム可能なプログラム言語である」ことがミソ。

開発も楽で良いよ。

柔軟性も高くて、拡張も楽で良いよ。

CLOSの例。

もしAOPが今後大流行するとしても何の変更もなしに取り入れることもできるよ。

Where It Began

Lispの歴史の話。

McCarthyの話。

1980年代のAIブームでは、Lispは専ら前人未踏の困難な問題に取り組む研究者の間で重宝された。

自動で問題を解析して問題を解くプログラムなど。

そして冷戦真っ只中であり、国防のためのシミュレーション等需要もあった。

AIバブルだったよ。

しかし、1980年当時にそういう問題は、マシンの能力の制限等でかなり厳しかった。

でも、がんばりました。

そんな努力のお蔭でCommon Lispはパフォーマンスについても良く考えられているよ。

Cと同程度のパフォーマンスもだせるよ。

そうそう、当時、Lispマシンってのもあったよ。

Lisp向きのチップの上にシステム全体もLispで記述されていて、その上は何でもLispで動いてたよ。

それで、1981年頃にLisp方言を纏めようという動きが現われたよ。

そんなこんなで、1986年にCommon Lispの最初の実装が登場したよ。

それで、ANSIでも、1996年にANSI Common Lispとして規格がまとまったよ。

このときには、CLOSとコンディションシステムが規格の範疇に入ったよ。

でもGUIとかマルチスレッド制御とかTCP/IPソケットについては範疇外だよ。

今じゃ、オープンソース関連の人々等が各自必要なものを作って補って使ってるって感じだよね。

Lispは、教科書に載るような非常に古典的な言語といえるかな。一方、実際的な問題を可能な限り効果的に解決できるモダンな言語でもありつづけているよ。

Lispにはインタープリタの実装しかなくて、遅くて、その上、何でも再帰で書かなくちゃいけないっていうのは、70年代始めのことでもいっているのかな。

But I learned Lisp Once, And IT Wasn't Like what you're describing

Schemeとの比較。

Who This Book Is For

Common Lispに興味がある人へ。

学校の勉強で、Lispに触れたけどいまいちだった人へ。

Perl, Python, Java, C, C#を使ってる実際家の人にもCommon Lispは「使える」ことを示したい。

この本は、文法とか言語の機構の説明に終始してないつもり。

最初のパートでは、ちょっとした実践的な例をまじえて言語の説明をして、残りでは、他の本がカバーしてないようなところを実践的な参考にできる例題を載せたよ。

スパムフィルタ、バイナリファイルのパーズ、MP3のカタログ、MP3のストリーミングと、それにウェブのインターフェースをつけたりとか。

この本をマスターしたなら、主要な言語機能には親しんだことになると思うし、Common Lispで些細でないプログラムを作れるようになる筈。

その先は各自探究して下さい。

2007-12-02

古えの分割代入機構的let

| 00:20 | 古えの分割代入機構的let - わだばLisperになる を含むブックマーク はてなブックマーク - 古えの分割代入機構的let - わだばLisperになる

いつものごとく、古いソースを眺めていて、古えのletの分割代入バージョンが詳しい説明付きソースをみつけました。

letが登場したのは、1979年位らしいですが、その当時からdestructuring-bindみたいな需要はあったらしく、このファイルでは、分割代入できるletが、そのままletという名前で、setqのバージョンがdesetqという名前で実装されています。

とりあえず、

(LET ((((A (B C) () . D) E () . F) (MUMBLIFY))
      TEMP
      (KEYNO '35)
      ANOTHER-TEMP)
  (DECLARE (SPECIAL F KEYNO))
  (COGITATE (LIST D E) A B C F))
  
; ==>

((LAMBDA (G0005 TEMP KEYNO ANOTHER-TEMP F E D C B G0007 A G0006) 
   (DECLARE (SPECIAL F KEYNO))
   (SETQ G0006 (CAR G0005))
   (SETQ A (CAR G0006))
   (SETQ G0006 (CDR G0006))
   (SETQ G0007 (CAR G0006))
   (SETQ B (CAR G0007))
   (SETQ G0007 (CDR G0007))
   (SETQ C (CAR G0007))
   (SETQ G0006 (CDR G0006))
   (SETQ D (CDR G0006))
   (SETQ G0005 (CDR G0005))
   (SETQ E (CAR G0005))
   (SETQ G0005 (CDR G0005))
   (SETQ F (CDR G0005))
   (COGITATE (LIST D E) A B C F))
 (MUMBLIFY) () '35 () () () () () () () () ())

という変換をするマクロで一時変数に値を移し移ししているのが面白いと思ったので、とりあえず、この説明を参考に自作して遊んでみました。


(defun des (bind sym)
  (let (vars)
    (values 
     (labels ((frob (bind sym)
		(cond ((null bind) nil)	
		      ((atom bind)
		       (push bind vars)
		       `((setq  ,bind ,sym)))
		      ((null (car bind))
		       `((setq ,sym (cdr ,sym))
			 ,@(frob (cdr bind) sym)))
		      ((and (atom (car bind)) (null (cdr bind)))
		       (push (car bind) vars)
		       `((setq ,(car bind) (car ,sym)))) ;last -1
		      ((atom (car bind))
		       (push (car bind) vars)
		       `((setq ,(car bind) (car ,sym))
			 (setq ,sym (cdr ,sym))
			 ,@(frob (cdr bind) sym)))
		      ('T (let ((carcons (gensym)))
			    (push carcons vars)
			    `((setq ,carcons (car ,sym))
			      ,@(frob (car bind) carcons)
			      (setq ,sym (cdr ,sym))
			      ,@(frob (cdr bind) sym)))))))
       (frob bind sym))
     vars)))

(defun cadrat (item)
  (if (consp item) (cadr item) nil))

(defmacro dlet ((&rest bind-specs) &body body)
  (let (cons-binds cons-vars) 
    (let ((vars (mapcar (lambda (item)
			  (if (consp item)
			      (if (consp (car item))
				  (let ((gs (gensym "VAR-")))
				    (multiple-value-bind (bf bv) (des (car item) gs)
				      (push bf cons-binds)
				      (push bv cons-vars))
				    gs)
				  (car item))
			      item))
			bind-specs))
	  (vals (mapcar #'cadrat bind-specs)))
      (let ((cons-vars (apply #'append cons-vars)) ;rebind
	    (cons-binds (apply #'append cons-binds)))
	`((lambda (,@vars ,@cons-vars)
	    ,@(if (eq (caar body) 'declare) ; declareを先頭に
		  `(,(pop body) 
		     ,@cons-binds
		     ,@body)
		  `(,@cons-binds ,@body)))
	  ,@(append vals (mapcar (constantly nil) cons-vars)))))))
;; マクロ展開
(dLET ((((A (B C) () . D) E () . F) (MUMBLIFY))
      TEMP
      (KEYNO '35)
      ANOTHER-TEMP)
  (DECLARE (SPECIAL F KEYNO))
  (COGITATE (LIST D E) A B C F))

;==>
((LAMBDA (#:VAR-3340 TEMP KEYNO ANOTHER-TEMP F E D C B #:G3342 A #:G3341)
   (DECLARE (SPECIAL F KEYNO))
   (SETQ #:G3341 (CAR #:VAR-3340))
   (SETQ A (CAR #:G3341))
   (SETQ #:G3341 (CDR #:G3341))
   (SETQ #:G3342 (CAR #:G3341))
   (SETQ B (CAR #:G3342))
   (SETQ #:G3342 (CDR #:G3342))
   (SETQ C (CAR #:G3342))
   (SETQ #:G3341 (CDR #:G3341))
   (SETQ #:G3341 (CDR #:G3341))
   (SETQ D #:G3341)
   (SETQ #:VAR-3340 (CDR #:VAR-3340))
   (SETQ E (CAR #:VAR-3340))
   (SETQ #:VAR-3340 (CDR #:VAR-3340))
   (SETQ #:VAR-3340 (CDR #:VAR-3340))
   (SETQ F #:VAR-3340)
   (COGITATE (LIST D E) A B C F))
 (MUMBLIFY) NIL '35 NIL NIL NIL NIL NIL NIL NIL NIL NIL)

;; 実行
(dlet ((((a (b c) () . d) e () . f) '((1 (2 3) () . 4) 5 () . 6))
      temp
      (keyno '35)
      another-temp)
  (declare (special f keyno))
  (list (list d e) a b c (symbol-value 'f) keyno another-temp temp))

;=>
((4 5) 1 2 3 6 35 NIL NIL)

いつもの如く行き当たりばったりなコード。

letだと名前がぶつかって厄介なので、dletという名前にしました。

元のソースは参考にしないで作りましたが、なんとなく仕組は分かったので、元はどうやって問題を解決しているのか探ってみたいと思います。

それと確かOn Lispにも似たようなものがあったのと思ったのでそっちも勉強したいです。