第23章「入力」

概要

ANSI Common Lispの第23章「入力」を説明します。

リーダーマクロ

第23章は「リーダー」、すなわち読取器についてですが、多くはプログラマでなく実装者向けの仕様なので、このページでは簡単に、今まで紹介していないリーダーマクロと一部の関数のみ紹介します。

リーダーマクロの仕組み・定義方法については第2章「構文」で紹介しました。リーダーマクロはCommon Lispにとって極めて重要な仕組みですが、自分で新しいリーダーマクロを追加すると混乱の原因になる可能性もあるので、基本的には消極的に使った方がいいでしょう。

今までに紹介していないマクロとして、bit-vectorを生成する#*と、読み込み時に評価を行う#., #+, #-を紹介します。

ビットのベクトル: #*

Common Lispで2進数表記で数を表すには、#bを使用します。
#b111
; => 7

これは正真正銘の数numberですが、数ではなくビットの列をそのままベクトルとして扱うこともできます。それがbit-vectorで、#*でリテラル表記します。
(length #*111)
; => 3

(aref #*111 2)
; => 1

読込時評価: #., #+, #-

ある種の情報は、コンパイル時でも実行時でもなく、読み込み時に評価したい場合があります。

例えば、日数を秒数に変換する場合を考えてみます。1日は24時間で、1時間は60分で、1分は60秒なので、1日の秒数を計算すると以下のようになります。
(* 60 60 24)
; => 86400

これは本質的に定数なので、日数を秒数に変換する関数は以下のように定義できます。
(defun day-to-second (n) (* n 86400))
; => DAY-TO-SECOND

しかし、86400という定数自体は、そのまま見ても意味が分かりにくいため、6024などの数をそのまま書いて定義したいとします。
(defun day-to-second (n) (* n 60 60 24))
; => DAY-TO-SECOND

ところが、この定義は先ほどの定義と比べて実行時に無駄な計算が入ります。86400は定数であるにも関わらず、毎回計算することになるからです。

このような場合に、読込時評価( read time evaluation )を行う#.マクロが役立ちます。
(defun day-to-second (n) (* n #.(* 60 60 24)))
; => DAY-TO-SECOND

#'day-to-second
; => #<FUNCTION DAY-TO-SECOND (N) 
;    (DECLARE (SYSTEM::IN-DEFUN DAY-TO-SECOND))
;    (BLOCK DAY-TO-SECOND (* N 86400))>

この#.リーダーマクロは、次に続く式を一つ読み取った時に、先行して評価します。評価結果は元の式があった場所に戻されます。そして、その値が最初からあったものとしてソースコードに組み込まれ、処理が続けられます。一度評価されると実行時には評価されないため、実行速度が遅くなることはありません。

同じように読取時評価を行いながら、条件分岐の機能を備えているのが#+#-です。これらのリーダーマクロは、次に続く式が*features*というスペシャル変数に含まれるかどうかを判断し、その次の式を評価するかどうかを分岐します。
;; GNU CLISPで試すと
#+clisp 'clisp #+sbcl 'sbcl
; => CLISP

;; SBCLで試すと
#+clisp 'clisp #+sbcl 'sbcl
; => SBCL

#+*features*に含まれる場合に2つ目の式を評価しますが、含まれない場合に評価するのが#-です。
;; CCLで試すと
#+clisp 'clisp #+sbcl 'sbcl #-(or clisp sbcl) 'something
; => SOMETHING

これらのリーダーマクロは、処理系依存を吸収するプログラムやライブラリを書く時には必須の機能です。

文字列との相互変換

一般的な入出力は第21章「ストリーム」で示した通りです。また、出力に関しては前章「出力」で紹介したformat関数が便利です。

ここでは、それ以外の関数を紹介します。

文字列から他のオブジェクトへ: read-from-string

文字列から他のオブジェクトに変換するには、read-from-string関数を使います。
(read-from-string "10")
; => 10
;    2

(read-from-string "3.14")
; => 3.14
;    4

(read-from-string "(a b c)")
; => (A B C)
;    7

(read-from-string "#(1 2 3)")
; => #(1 2 3)
;    8

(read-from-string "(+ 1 2)")
; => (+ 1 2)
;    7

(eval *)
; => 3

小数点数である文字列を'double-float型の数に変換するには、あらかじめスペシャル変数*read-default-float-format*を操作しておきます。
(type-of (read-from-string "3.14"))
; => SINGLE-FLOAT

(setq *read-default-float-format* 'double-float)
; => DOUBLE-FLOAT

(type-of (read-from-string "3.14"))
; => DOUBLE-FLOAT

他のオブジェクトから文字列へ: write-to-string

逆にオブジェクトを文字列に変換するにはwrite-to-string関数を使います。
(write-to-string 10)
; => "10"

(write-to-string 3.14)
; => "3.14"

(write-to-string '(a b c))
; => "(A B C)"

(write-to-string #(a b c))
; => "#(A B C)"

(write-to-string #'+)
; => "#<SYSTEM-FUNCTION +>"

0 件のコメント :

コメントを投稿