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-10-16


Paul Graham氏のユーティリティ その5

| 03:25 | Paul Graham氏のユーティリティ その5 - わだばLisperになる を含むブックマーク はてなブックマーク - Paul Graham氏のユーティリティ その5 - わだばLisperになる

PG氏のユーティリティを読んでみることの5回目。

ファイルの読み書きユーティリティ編です。

Lisp utilities that were not included in On Lisp or ANSI Common Lisp

下準備

(defconstant eof (gensym))

PG氏は、eofっていう定数を定義するらしい。どういう御利益があるのかまだ自分には推し量れないのだった。

お題

clear-file

暗記で再現

(defun clear-file (file)
  (aif (probe-file file) (delete-file it)))

とりあえずできた。その名の通りファイルを消去するユーティリティ。

;; 動作
(clear-file "/tmp/foo.txt")
=> ファイルが消えました。

お題

do-chars

暗記で再現

(defmacro do-chars (var str &body body)
  (with-gensyms (g)
    `(let ((,g ,str))
       (do ((,var (read-char ,g nil eof) (read-char ,g nil eof)))
	   ((eql ,var eof))
	 ,@body))))

とりあえずきた。暗記するときにstrはストリングと覚えたので再現するとき混乱してしまった、streamの略だった。

;; 動作
(with-input-from-string (stream "foo bar baz")
  (do-chars i stream
    (print i)))
#\f 
#\o 
#\o 
#\  
#\b 
#\a 
#\r 
#\  
#\b 
#\a 
#\z 

お題

do-stream

暗記で再現

(defmacro do-stream (var str &body body)
  (with-gensyms (g)
    `(let ((,g ,str))
       (do ((,var (read ,g nil eof) (read ,g nil eof)))
	   ((eql ,var eof))
	 ,@body))))

とりあえずできた。do-charsとは、read-charを使うかread-を使うかの違いだけ。

;; 動作
(with-input-from-string (stream "foo bar baz")
  (do-stream i stream
    (print i)))

FOO 
BAR 
BAZ 

お題

in-case-error

暗記で再現

(defmacro in-case-error (expr err)
  (with-gensyms (var cond)
    `(bind (,var ,cond) (ignore-errors ,expr)
       (if (typep ,cond 'error)
	   (progn
	     ,err
	     (error ,cond))
	   ,var))))

とりあえずできた。自分がファイル関係の操作に馴れてないってのもあって、どういう意図があってこういう構成になっているのか分かっていないのだった。

順番がファイルと前後してしまうけれど、後ででてくるfile-readが依存しているので、先に読むことに。

;; 動作
(in-case-error 
 (load "/tmp/f") ;存在していないファイル
 (format *error-output* "hello!"))
=> hello!

お題

with-infile

暗記で再現

(defmacro with-infile (var fname &body body)
  (with-gensyms (f)
    `(let ((,f ,fname))
       (in-case-error
	(with-open-file (,var ,f :direction :input)
	  ,@body)
	(format *error-output* "Error reading from ~S.~%" ,f)))))

とりあえずできた。with-open-fileをちまちま書くのが面倒だったので作られたようなマクロ。元ファイルには何故か使われていないgensymの宣言があってちょっと迷った。

お題

file-read

暗記で再現

(defun file-read (path)
  (awhen (probe-file path)
    (with-infile str it
      (read str nil nil))))

とりあえずできた。上記2つはfile-readが依存していたので芋蔓式に書くことになった。しかし、これはファイルの先頭しか読まないんだけれど、どういう風に使うんだろう…。

お題

file-read-line

暗記で再現

(defun file-read-line (path)
  (awhen (probe-file path)
    (with-infile str path
      (read-line str nil nil))))

とりあえずできた。その名の通り上記file-readのread-line版。これも使い方が良く分からない。

これまで気付いたちょっとしたこと

PG氏に一貫しているところとして、

  1. eqを使わない(eql以上を使う)
  2. マクロでは&bodyを使わず、&restで統一
  3. ファイルユーティリティ系では、アナフォリック系条件式で、probe-fileをして、itで読み込ませるのを定石としているらしい。
  4. gensymで使われるシンボルはとりあえず、g次に順番で、hとかが多い。

他に自分が感じるところとしては、日常で使うシェルスクリプト的感覚で、思い付いたらとりあえずマクロ作って使ってみてるんじゃないかと思ったりします。