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 |

2008-03-16

第3回 PAIP 本読書会に参加してきました

| 02:25 | 第3回 PAIP 本読書会に参加してきました - わだばLisperになる を含むブックマーク はてなブックマーク - 第3回 PAIP 本読書会に参加してきました - わだばLisperになる

今日は、Peter Norvig氏の名著「Paradigms of Artificial Intelligence Programming」の読書会に参加して来ました。

初心者の自分に果してついて行けるかどうかということと、まだ本を入手していないということで、今回は見学というスタンスでしたが、欠席の方のPAIPを貸して頂けることになったので普通に参加させて頂くことになりました。

内容は、PAIPの内容を各自順に音読して訳しつつ進めるというものでした。

人前で英語で内容を即時訳して行くのは高校以来位であり、また内容も予習していないので、頭が真っ白になり何を読んでいるのか全然把握できていませんでしたが、周りの皆様に助けて頂きどうにか進めることができました。

頭が真っ白だったので、記憶が曖昧なのですが、以下適当に纏めてみました。

2. A Simple Lisp Program

  • ここでは、Lispを使って英文を生成させつつ、抽象化をの技法の入門といった感じ。
  • 人に読みやすいプログラムを書くということを解説しつつ、また、そうすることで、Lispベースのちょっとした専用言語のようなものができることを示されているような、いないような。

3. Overview of Lisp

とりあえず、Norvig家家訓として、

コードは、

  1. 明確に書くべし
  2. 抽象化を活用すべし
  3. 簡潔に書くべし
  4. 予め用意されているもの(関数等)は活用すべし
  5. 一貫性を持たせるべし

の6訓があるようです。

  1. defun、defvar、defparameter等のdef-系の説明で、defvar、defparameter、defconstantの使い分けについてと、defstructの機能の解説。
    • setfの解説。値を代入するだけでなく、値の場所を指定することにより、その場所を展開して値を代入することが可能であることを解説。また、ユーザがsetfの拡張を定義することが可能。途中、佐野さんより、define-setf-methodという表記は古いというツッコミがはいる。ANSI Common Lispでは、define-setf-expanderに名称変更。また、(defun (setf foo)〜)という定義も可能だよね、というツッコミもありつつ。
  2. 同じことをするにも複数の書き方があるが、どれが適切かを良く考えることと、どういう表現をするにしても一貫性をもって表現することが大事であることを強調
    • 繰り返し機構の解説と比較
      • 以上を踏まえ具体例何種類かのループでlengthを作成してみせる。妙な書き方も解説しつつ、何がプログラムの表現として適切かを探る。基本的には、dolist、dotimes、dodo系のものと、mapcar等のmap系のもの、再帰を比較する。副作用前提のもの、そうでないもの等で表現が変わることを解説。また、特定の用途に特化したfindや、count-if等の仲間も解説し、それぞれ、一長一短を解説。

ちなみに、色々あるlengthの実装で、自分が一番妙だなと思ったのは、

(defun length8 (list)
  (if (null list)
      0
      (+ 1 (position-if #'true list :from-end t))))

で、これは、かなり捻らないと出てこない発想じゃないかと思います。読書会の後で話題にもなりました。

また、関連するところでは、Norvig先生は、trueという引数を与えられたら無条件にtを返すという関数を定義しています。

(defun true (&rest ignore) t)

確か、こういう用途には、何かあった筈…という話題も読書会の後の飲み会で出ましたが、家で調べたらconstantlyでした。

constantlyは関数を返す関数で、

(mapcar (constantly t) '(1 2 3 4 5 6))
;=> (T T T T T T) 

のように使えます。こういう場所位でしか使いどころがないと思うので、個人的には是非使ってあげたい関数かなと思います(笑)

また、例に挙げられていない方法でlengthを考えてみる、というのもちょこっと話題になったのですが、自分は、

(defun length-1192 (lst)
  (apply #'+ (mapcar (constantly 1) lst)))

というのを考えてみました。内容を全部1に置き換えて最後に+を適用するというものです。

次回は、続きで、Macroのところから。

読書会後の飲み会的な会

今回、CL風味の方が結構集まった様子。折角なので、会場近所の店で、色々お話することになりました。

自分としても色々御聞きしたいことがあったのですが、自分は、ひきこもっているということもあり、あまり話をしていない反動で、延々と自分の好きな話題を喋りまくっておりました。ほんとに、すいません(笑)

こういう会をきっかけに、CLが草の根的にどんどん盛り上がっていったら良いなと思いつつ帰宅しました。

2008-01-25

Practical Common Lisp (23)

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

Practical Common Lisp 第5章5. Functionsを読んでメモしてみています。

大分間が開いてしまいました。かなり、まったり進行です。

Mixing Different Parameter Types

  • 各種ラムダパラメータは組み合わせられるが、リストに現われる順番には決まりがある。下記の順番である必要がある。
    1. &optional
    2. &rest
    3. &key
  • これらのパラメータは組み合わせられるものの直感的でない動作をするものもあるので注意が必要
1.
((lambda (x &optional y &key z) (list x y z)) 1 2 :z 3)

2.
((lambda (x &optional y &key z) (list x y z)) 1 :z 3)
;=> error

3.
((lambda (x &optional y &rest z) (list x y z)) 1 3)
;=> (1 3 nil)

4.
((lambda (&rest rest &key x y z) (list rest x y z)) :x 1 :y 2 :z 3)
;=> ((:X 1 :Y 2 :Z 3) 1 2 3) 

個人的には、2のエラーは&keyと&restが同じ場所に格納されると考えると合点が行くんじゃないかと思っています。

2008-01-24

Dick Gabriel on Lisp

| 16:50 | Dick Gabriel on Lisp - わだばLisperになる を含むブックマーク はてなブックマーク - Dick Gabriel on Lisp - わだばLisperになる

昨日Redditを眺めていてみつけたんですが、Richard P. Gabriel氏のPodcastが公開されています。

Sunで働いてると思っていたんですが、いつの間にやらIBMで働いてたんですね。

早速聴いてみたんですが、LISPの歴史と特徴が語られる一時間。

英語が苦手な私は、あんまり聴き取れなかったんですが、LISPに関連することは一通り網羅されつつ、非常にうまく纏まっているので手短にLISP周辺のことを知るには最適じゃないかなと思いました。

2008-01-15

Lisp初心者がお勧めするLisp入門にお勧めなL-99

| 18:16 | Lisp初心者がお勧めするLisp入門にお勧めなL-99 - わだばLisperになる を含むブックマーク はてなブックマーク - Lisp初心者がお勧めするLisp入門にお勧めなL-99 - わだばLisperになる

ひげぽんさんのところのScheme情報リンク集で取り上げて頂いてからRSSのフィードの登録も急増し、凄い勢いだなあと思っているわけなのですが、何を隠そう私は、完全なるプログラミング初心者かつLisp初心者であり、しかも完全独学で誰に教わったという訳でもないので、何か悪いコードを書いても、コンパイラにしか怒られたことのない、そんな甘えん坊さんな完全趣味プログラマなのです。いや、お恥ずかしい…というか申し訳ない…。

UNIXのワークステーションを好んで使っていたために、シェルスクリプトは日常生活で使う分位は書けますが、Perlも、RubyもSchemeも、JavaもCも入門書の第一章位辺で挫折しました。

そんな私ですが、Lispの括弧と前置記法には全く抵抗がなかったためか、Lispや、Schemeは何度か繰り返してトライしては挫折を繰り返していました。

最初は、Lispに実用性(シェルスクリプトの代替となるもの)を求めていて、日頃身近に使うことにすれば自然にば身に付く筈と安易に考えていたのですが、シェルスクリプトから乗り換えようというには、GaucheやScshは、至極当然ながらあまりにもScheme的で、その表現方法を知らねばならず、またCommon Lispは、それに加えて、UNIX上でスクリプト言語として使うのは、OSを含めた文化の違いで無理ではないにしろ、手軽では無いのでこれまた挫折しました。勿論Lispマシンの上では、Lispが一番簡便なスクリプト言語ですが(笑)

そんなこんなしている去年の今頃、del.icio.usのlispタグのところに「L-99: Ninety-Nine Lisp Problems」が集中的に登録されていたので、ちょっと興味を持って挑戦してみることにしました。

これは、Lispの問題が大体難易度順に並べられた問題集です。L-99といいつつ84問しか無いのですが、元はProlog用に作られたもので、「401 Unauthorized」がオリジナルのサイトです。

この問題集には、リスト操作の問題が沢山でてくるのですが、初心者にも比較的手頃で、割と、次へ次へという感じで進むことができて、そんなこんなしているうちに、それなりにリスト操作も身に付く気がします。

それで、リスト操作が身近になってくると、マクロを書くことも身近になって来ました。結局のところLispの伝統的なマクロは、プログラムというリストを操作して目的の構造を作るということだからかもしれません。

そして、リスト操作/マクロが身近になってくるとLispの制御構造も身近になって来て、なんとなくLispで何かを表現できるようになってくる気がします。(関数型言語としてのLispが身近になるというよりは、リスト操作言語としてのLispが身近になるという感じなのかもしれませんが…。)

それと、L-99には、リスト操作だけでなく、それなりにコンピュータサイエンス的な問題もちりばめられていたりするのも良い感じです。

しかし、L-99には、若干の問題があって、

  1. 解答が全部完成していない
  2. 後半は、P-99からそのまま転載していたりするのでProlog用語そのまま

だったりします。

しかし、元のP-99を参照すればどうにかなるので、まあ大丈夫かなと。

もし自分のように、何年もLisp周辺をずっとぐるぐる回ってはいるんだけど、実際にはLispでプログラムを書くのが身近に感じられず、結局手着かず…という人がいたならば、L-99は良いとっかかりになると思うので個人的にお勧めしたいです。

それで、開発環境ですが、やはり、初心者をサポートすると言う意味でもSLIMEは強力だと思います。

コンパイルして問題があるソースの個所を色付きで教えてくれたり、関数名をガンガン補完してくれたり(例えばppcre:bindをppcre:register-groups-bindまで展開してくれる)してくれるので非常にお勧めです。

…そういう私ですが、66問目ではまってしまい、それ以上進めずにいます(;´Д`)

おまけ

L-99をPerl6で解くという試みもあるようです。

2008-01-09

Practical Common Lisp (22)

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

Practical Common Lisp 第5章5. Functionsを読んでメモしてみています。

半挫折気味です(笑)、気力が…。

Keyword Parameters

  • オプショナル引数は便利だが、キーワード引数を使うとそれだけでは解決できないこともできる。
  • &optional、&restの後だったらどこでも使える。
(foo)                ==> (NIL NIL NIL)
(foo :a 1)           ==> (1 NIL NIL)
(foo :b 1)           ==> (NIL 1 NIL)
(foo :c 1)           ==> (NIL NIL 1)
(foo :a 1 :c 3)      ==> (1 NIL 3)
(foo :a 1 :b 2 :c 3) ==> (1 2 3)
(foo :a 1 :c 3 :b 2) ==> (1 2 3)
  • 上記のように引数定義の順番ではなく、キーワードの名前と指定した値が対になる。キーワードを省略した場合は、&optionalのようにデフォルト値が使われる。また、同様にsupplied-pも使用可能。
  • 外向のインターフェースとしては、指定した名前を使用し、内部では別の名前で処理したい場合、下記のように書ける。
(defun foo (&key ((:apple a)) ((:box b) 0) ((:charlie c) 0 c-supplied-p))
  (list a b c c-supplied-p))

;=> (foo :apple 10 :box 20 :charlie 30) ==> (10 20 30 T)

; :appleで値を受けとるが、内部では、aに結び付けられているので、aで参照。

PCLとは無関係な個人的な深追いと混乱メモ

CLtL2を読んでいたら、キーワードについてさらに詳しい(というか自由度の高い)使い方が説明されていたのでメモ

PCLでの例のように(:apple a)という風に記述すれば、内部では違う名前で扱えるわけですが、

(defun foo (&key ((:apple a) 0))
  `(apple -> ,a)

(foo :apple 10)
;=> (apple -> 10)

キーワードで使うシンボルは、キーワードパッケージのシンボルでなくても良いみたいで、

(defun foo (&key ((apple a) 0))
  `(apple -> ,a )

とも書けます。

それで、呼び出しはどうなるのかと言えば、

(foo 'apple 1)
;=> (apple -> 1)

(foo :apple 1)
;=> エラー

となり、定義に使用したキーワードが属するパッケージのシンボルである必要があるようです。

上記のようにパッケージ名を省略すれば、カレントパッケージになるわけですが、見た目で混乱するので、たとえば、cl-userのシンボルを使うとすると、

(defun foo (&key ((cl-user::apple a) 0))
  `(apple -> ,a )

(foo 'cl-user::apple 1)
;=> (apple -> 1)

となります。

(defun foo (&key foo)
  (print foo))

をややこしく書くと、

(defun foo (&key ((keyword::foo foo)))
  (print foo))

ということみたいです。

しかし、キーワードにキーワードパッケージ以外を指定するような状況ってどういうのがあるんでしょう…。

2008-01-06

BBN-LISPのADVICE

| 22:11 | BBN-LISPのADVICE - わだばLisperになる を含むブックマーク はてなブックマーク - BBN-LISPのADVICE - わだばLisperになる

comp.lang.lispを眺めていたら、SymbolicsのLispマシンのマニュアルがbitsaversにアップされたとのことで、早速ダウンロードして眺めたりしておりました。

それで、bitsaversの他のディレクトリを探索していたら、BBN-LISPのマニュアルがありました。

BBN-LISPはINTERLISPの前身ともいえる存在です。

それで、Symbolicsそっちのけで眺めてみていたら、19章(P423)にADVICEという文字が。

Emacsを使っている方だったらdefadviceは知っているかと思うのですが、それと同じパターンの機構で、ある関数にフックをかけてカスタマイズするという仕組のようです。

CLOSならビフォアとアフターメソッド的ともいえるかもしれません。BBN-LISP、INTERLISPには、Daniel G. Bobrowが深く関与していますが、CLOSにも関与しているようなので、そういう影響もあるのかもしれません。BBN-LISP→INTERLISP→COMMON LOOPS→CLOSという流れ。

しかしそれよりも、このマニュアルが出たのは、1971、2年ということで、こういう仕組みが35年前に考えられていて実装されていたということに驚かされました。

他にも先進的な試みが沢山あるようで、INTERLISPの特徴は殆どBBN-LISPの時点で既に実装されているようです。

こんなことに感動しているのは自分くらいかもしれませんが、とりあえず記念にメモ(´▽`*)

Practical Common Lisp (21)

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

Practical Common Lisp 第5章5. Functionsを読んでメモしてみています。

Rest Parameters

  • 引数の扱いには、様々あるが引数の数を可変にしたいことがある。例えば、FORMATは、2つ以上の引数を取るが、2つ目以降は必要に応じて与える引数が増減する。+も同様。0個の場合もあり、(+)は0となる。
(format t "hello, world")
(format t "hello, ~a" name)
(format t "x: ~d y: ~d" x y)
(+)
(+ 1)
(+ 1 2)
(+ 1 2 3)
  • 必須引数や、オプショナル引数等をやりくりして対応することもできるが、対応させるのは困難。。ちなみに取れる最低限50以上は引数が取れることを保証することが規格で定められている。実装により上限は異なるが、CALL-ARGUMENTS-LIMITで処理系の上限を確認できる。(SBCL 1.0.13では、1,152,921,504,606,846,975だった)
  • このような問題を解決するのが、&RESTパラメータで、&RESTに指定された変数がリストとなり、以降の引数が格納される。FORMATや、+は下記のように定義できる。
(defun format (stream string &rest values) ...)
(defun + (&rest numbers) ...) 

2008-01-04

PAIP読書会

| 00:50 | PAIP読書会 - わだばLisperになる を含むブックマーク はてなブックマーク - PAIP読書会 - わだばLisperになる

PAIP読書会が開催されることになったとのことで、早速PAIPを持ってない私もメーリングリストにだけは、登録してみたのですが、告知メールが今日届きました。

--

「Paradigms of Artificial Intelligence Programming:

Case Studies in Common Lisp」

by Peter Norvig

の読書会を開催します。とりあえず第1回を下記の日程と場所で実施し、

そこでいろいろ相談しながら、ゆる~くやり方を決めていきたいと考え

ています。

日時: 2008年1月12日(土) 13:00~18:00

場所: 株式会社タイムインターメディア2F大会議室

東京都新宿区坂町26-27 インテリジェントプラザビル 2F

参加費: 無料

参加条件: 特にありませんが、上記の本とCommon Lisp処理系をインストールした

ノートパソコンを持参することをお薦めします。

その他: 持ち込みネタ(PAIP本に関わる発表とかデモとか大歓迎です。)

質問や提案があれば、WiLiKiのページかこのメーリングリスト、あるいはLingrのGauche部屋に流してもらえるとありがたいです。

また、この告知自体の転載を歓迎します。

--

とのことで、参加しようかどうか悩んでいます。PAIP持ってないしな〜。

この本の中のコードは、公開されてるから、それとPCを持参すれば少しはましかな、うーん。

まあ、速攻PAIP買えよって話なんですけど、この本自分にはちょっと難易度が高すぎるんじゃないかなと思いまして。やっぱり、とりあえず様子見かな(笑)

Practical Common Lisp (20)

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

Practical Common Lisp 第5章5. Functionsを読んでメモしてみています。

Optional Parameters

オプショナルパラメータの話

  • 全部の関数が予め決められた数の引数を扱うなら良いが実際的には、例えば、パラメータに初期値が設定されていて、呼び出し時にもし必要な場合、パラメータを指定したりできると便利。
  • そういうときには、&OPTIONALが使える。
(defun foo (a b &optional c d) (list a b c d))
  • 上記の場合、&optionalより前のaとbは必須パラメータとなり、それより後は、オプショナルになる。
  • 呼び出し時にオプショナル引数に値を渡さないとNILで初期化してくれる。
(foo 1 2)     ==> (1 2 NIL NIL)
(foo 1 2 3)   ==> (1 2 3 NIL)
(foo 1 2 3 4) ==> (1 2 3 4)
  • NILで初期化するのは分かったが、他の値で初期化して欲しい場合、明示的に指定する方法があり、(変数名 初期値)という風にリストにして記述する
(defun foo (a &optional (b 10)) (list a b))

(foo 1 2) ==> (1 2)
(foo 1)   ==> (1 10)
  • 引数の値を他の引数から算出して設定したい場合もある。例えば、矩形を描く関数を定義するとして、基本的にデフォルトは正方形を描くものとし、高さは、明示的に与えられない限り、幅から算出されるとしたい。その場合、先に決定した値を後方で参照して初期値として使用することができる。
(defun make-rectangle (width &optional (height width)) ...)
  • 偶に、引数が明示的に与えられたのか、それとも初期値が使われたのかを判別したい場合がある。その場合には、(変数名 初期値 指定されたかどうかを判別する変数名)という形式で記述する。
(defun foo (a b &optional (c 3 c-supplied-p))
  (list a b c c-supplied-p))

なんとなく演習

(defun オプショナルな気分 (&optional (ans nil))
  (print
   (if ans
       "オプショナルな気分なんですね?"
       "オプショナルな気分ではないのすね?")))

(オプショナルな気分 nil)
"オプショナルな気分ではないのですね?" 

(オプショナルな気分)
"オプショナルな気分ではないのですね?" 
;; いや、そういうわけではないのだが…。

;; 上の定義では、区別が付かないので改善
(defun オプショナルな気分 (&optional (ans nil ans-supplied-p))
  (print 
   (cond ((and ans-supplied-p (not ans)) "オプショナルな気分ではないのですね?")
	 (ans "オプショナルな気分なんですね?")
	 ('T "なんともいえないのですね?"))))

(オプショナルな気分 nil)
"オプショナルな気分ではないのですね?" 

(オプショナルな気分)
"なんともいえないのですね?" 
;; いや、そういうわけでもないのだが…。

--

質問に答えること自体は任意で、否定したい場合は、NILで答えるとした場合、オプショナル引数を省略したためにデフォルト値のNILが渡って来たのか、それとも明示的にNILで答えられたのか区別できないので、supplied-pを利用するという無理矢理なケースを捏造してみる。

2008-01-02

Practical Common Lisp (19)

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

Practical Common Lisp 第5章5. Functionsを読んでメモしてみています。

Function Parameter Lists

  • パラメータリストの主な役割とはもちろん引数をどう受けとってどのように関数本体の処理に渡すかということ
  • パラメータリストが変数名のみで構成された単純なリストの場合、それぞれの要素は必須パラメータと呼ばれる。
  • この場合、関数が呼ばれる際は、指定したパラメータそれぞれに対応する引数が必ず割り当てられなければならない。対応する引数は、各パラメータに束縛される。関数の呼び出し時に渡される引数が多くても少なくてもエラーとなる。
  • しかしながら、Common Lispのパラメータリストにはより柔軟な引数処理の指定もできる。
  • まず、前述の必須パラメータに加えて、省略可能なオプショナルパラメータがある、また、引数をリストで纏めてリストで受けとることもできる。また、引数の位置の出現位置の順番で対応を指定するのではなく、指定したキーワードに結び付けて取得する方法、というようにCommon Lispには色んな場合に対応できる方法を提供している。

2008-01-01

Practical Common Lisp (18)

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

やっとこさPractical Common Lisp 第5章5. Functionsです。この章も文字だけみたいなので、読んでメモするという感じで…。

5. Functions

  • 文法と意味論の後は、Lispの3つの基本要素である、関数、変数、マクロをみてゆく。
  • 3章で既にこれら3つは使用していたが、うわべだけでどのように機能しているかの詳細には立ち入らなかった。
  • 今後の2、3章で、これらの話題を扱う。他の言語で対応する諸機能と対象させて考察してみたり等々…。
  • Lispの大半は、関数で成り立っていて、言語規格で定められたものの3/4は関数が占める。
  • すべての組込みデータ型は、関数がどのようにデータを扱うかということにより定められている(?)
  • CLOSでさえ、関数/総称関数の上に成り立っている。これらは、16章で扱う。
  • Lisp流儀にはマクロというものが非常に重要なものになるが、すべての真の機能性は関数によってもたらされているものである。

Defining New Functions

  • 通常関数定義にはDEFUNが用いられ下記のように書かれる
(defun name (parameter*)
  "Optional documentation string."
  body-form*)
  • どんなシンボルでも関数名に使える。通常、アルファベットとハイフンのみが多い。それ以外は特定の命名規則に則って使用されることが多い。
  • 例えば、ある形式のものを他の形式に変換するような関数の場合、"-"で連結されて一つの名前になることがある。文字列をウィジェットに変換する場合は、string-widgetといった風。
  • 重要な事項としては、2章でも述べたが、語句の連結に、"-"を使いアンダースコアや、キャメルケースで書くことはしないということで、frob-widgetは、frob_widgetや、frobWidgetより好ましい。
  • 関数の引数リスト部は変数がどのように関数に渡されるかを定義する。引数を取らない場合は、空リストの()で表記される。
  • 引数の取り方の指定は、色々ある。必須とされるもの、オプション扱いのもの、複数の指定、キーワードの指定。
  • 引数のパラメータリストの次が文字列の場合、その文字列は、その関数を説明に使われるドキュメント文字列となる。関数が定義された場合、ドキュメント文字列は、DOCUMENTATIONで参照できるように定義される。
  • 最後に関数の主となる定義部分である本体になる。式は順に評価され末尾で評価された式が関数の値となるか、RETURN-FROMを用いて指定した値で関数を脱出することもできる。
  • 2章で記のような関数定義をしたが、これを詳細にみると下記のようになる。
    1. 関数の名前:hello-world
    2. 引数:()リストなので、引数はとらないことがわかる
    3. ドキュメント文字列はなし
    4. 一つの式のボディ
(defun hello-world () (format t "hello, world"))
  • もう少し複雑なものをみると、下記のようになる。
    1. 関数の名前は、verbose-sum
    2. 2つの引数を取り、xとyに束縛される。
    3. ドキュメント文字列を持つ。
    4. ボディは、2つの式から成り、返り値は、+が引数を処理した値が、verbose-sum全体の返り値となる。
(defun verbose-sum (x y)
  "Sum any two numbers after printing a message."
  (format t "Summing ~d and ~d.~%" x y)
  (+ x y))

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)))

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

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)

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

2007-12-28

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-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-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では上記のようになんでも関数処理の流れとして扱い、一貫したものになっていたりする。