CL-WHO - Yet another Lisp markup language を訳してみた

HTMLジェネレータ、CL-WHOのドキュメントを訳してみました。

※どうも最後の方が途切れてるようです。原因不明。容量制限か? と思って、AAを消してみたり、単独記事を立てても途切れます。コードが多すぎるせいでしょうか? そのうち分割して記事を書きます。(2012/07/12)

注意

  • 原文はこちら:http://weitz.de/cl-who/
  • この訳は割とてきとーで、不正確な箇所、怪しい箇所、未訳の箇所があります
  • そのためツッコミ歓迎です
  • 転載改変お好きにどうぞ
  • 島は移動しません


Abstract

世の中にはたくさんの Lisp マークアップ言語がありますが、 -- どんなプログラマも1度くらいは書いたことがあるでしょう -- CL-WHO (WHO とは "with-html-output" の略です。他に適当な頭字語がなかったので) もまた、良くも悪くもそういうものです。 それらは、コードと交じり合ったS式を(X)HTMLやXMLや他のあれこれに変換するための便利な手段を提供するという点では、どれも程度の差はあれ似通ったものですが、シンタックス、実装、APIにおいて違いがあります。そのため、もしあなたが選択をしていないならば、CL-WHOにたまたまはじめに出くわしたという理由でこれを使い始める前に代替案をよく調べたほうがよいでしょう。(Was that repelling enough?)。もしあなたがちょっと違ったやり方を探しているなら、 HTML-TEMPLATE も見るといいでしょう。

私はこれを 2002 年に書きました。そのときは少なくとも、Tim Bradshaw の htout と、AllegroServe の HTML generation facilities が Franz Inc. の John Foderaro が使えました。実のところ、私はなぜ私のライブラリを書かなければならなかったのか覚えていません -- たぶんたいして時間もかからず、なおかつ楽しかったからでしょう。シンタックスhtout にインスパイアされたものですが、ちょっと違ってます。


CL-WHO は、なるべく固定長の文字列を作り出す点で効率的なコードを作ろうとしています。言い換えれば、 CL-WHO のマクロによって生成されるコードは、ユーザによりマクロに恣意的に挿入されたコードが、定数部分を出力するためのWRITE-STRINGフォームの羅列(sequence)の中に点在したものになります*1CL-WHO は2つの隣り合う WRITE-STRING フォームが存在しないようにします -- 下の例を見てください。CL-WHOの出力は XTML(デフォルト)あるいは 'plain' (SGML) HTML です -- それはあなたが HTML-MODE に何をセットするかによりますが。

CL-WHO はポータブルであり、すべての Common Lisp 実装で動作することを目的としています。もしあなたが問題にいきあたったら、お知らせください。

これは BSD-stype ライセンスと共に配布されているので、基本的に、あなたはこれを使って好きなことができます。

CL-WHO は、例えば clutuHeike Stephan によって使われています。

Download shortcut: http://weitz.de/files/cl-who.tar.gz.



Contents

Example usage

*HTTP-STREAM* があなたのウェプアプリケーションが書き込みをサポートしているストリームだと仮定しましょう。このわざとらしいコード片は、 CL-WHO によって生成されるLispコードとHTMLアウトプットを一緒にしたものです。

(略)


Download and installation

(略)



Support and mailing lists

質問、バグレポート、機能リクエスト、改善、あるいはパッチについては、the cl-who-devel mailing listを使ってください。もしあなたが未来のリリースについて通知を受けたいのなら、the cl-who-announce mailing listに申し込んでください。これらのメーリングリストcommon-lisp.netのおかげで使用可能になりました。

もしパッチを送りたいならば、これを最初に読んでください


Syntax and Semantics

CL-WHOは本質的に一つのマクロ WITH-HTML-OUTPUT である。これは、その包むところのコード(すなわちbody)を次なる変換ルールにしたがって別のものに変換する(私達はこれをtransformation rulesと呼ぶ)。

ルール1
文字列はそのまま

文字列は文字通りプリントされる。より正確に言えば、その文字列をユーザが与えたストリームに書き出すフォームに変換される。

"foo" => (write-string "foo" s)

(ここでいう、またこのドキュメントの残りにおいて、赤い矢印*2は「左辺を右辺に相当するコードに変換する」ことを意味する。相当する、とは、すべてのアウトプットが「正しい」ストリームに送られるという意味である)

ルール2
キーワードはタグになる

キーワードで始まるリストは、次のルールに従い、同じ(通常、ダウンケースされた)名前のタグに変換される。

ルール2.1
空のタグ

もしリストがキーワードの他に何も含んでいなければ、結果のタグは空になる。

(:br) => (write-string "<br />" s)

HTML-MODE:SGMLにセットされている場合、空要素は次のように書かれる。

(:br) => (write-string "<br>" s)
ルール2.2
タグと属性

キーワードの後には別のキーワードを続けることができる。続けられたキーワードは属性名として解釈される。さらにその次のフォームは属性の値とされる(もし次のフォームがなければ、それはNILであったかのように振る舞う)。属性値を示しているフォームは次のように解釈される。(注意! 属性に関する振る舞いはバージョン0.3.0以前とは互換性がない!)

  • もしそれが文字列ならば、文字通り印字される。
(:td :bgcolor "red") => (write-string "<td bgcolor='red' />" s)
  • もしそれが T であり、HTML-MODE:XMLならば、属性値は属性名になる。(XHTMLの慣習で、HTMLにおける属性値のない属性はこう書く)*3
(:td :nowrap t) => (write-string "<td nowrap='nowrap' />" s)

HTML-MODE:SGML ならば、次のようになる。

(:td :nowrap t) => (write-string "<td nowrap>" s)
  • もし NIL ならば、属性は完全に除去される
(:td :nowrap nil) => (write-string "<td />" s)
  • もし定数フォームなら、評価結果がフォーマット文字列"~A"によってプリントされるような文字列として、マクロ展開時に結果文字列に挿入される。
(:table :border 3) => (write-string "<table border='3' />" s)
  • それ以外のフォームの場合そのまま残され、実行時にprincを使って表示される。ただし、TNILは上の通り処理する。(正しい表示コントロール変数を用意するのはアプリケーション開発者の仕事である)。
;; simplified example, see function CHECKBOX below
;; note that this form is not necessarily CONSTANTP in all Lisps

(:table :border (+ 1 2)) => (write-string "<table border='" s)
                              (princ (+ 1 2) s)
                              (write-string "' />" s)
ルール2.3
複数の属性

ひとたび属性/値ペアが完成すれば、別のペアがそれに続けて書ける。
言い換えれば、属性値の次のフォームがキーワードならばそれはまた属性名として扱われる。

(:table :border 0 :cellpadding 5 :cellspacing 5)
      => (write-string "<table border='0' cellpadding='5' cellspacing='5' />" s)
ルール2.4
タグの要素

タグ名あるいはキーワードでない属性値に続くはじめのフォームは、タグの要素の開始を定める。そのフォームと、続くすべてのフォームは今記述しているtransformation rulesにしたがう。

(:p "Paragraph") => (write-string "<p>Paragraph</p>" s)
(:p :class "foo" "Paragraph") => (write-string "<p class='foo'>Paragraph</p>" s)
(:p :class "foo" "One" " " "long" " " "sentence")
    => (write-string "<p class='foo'>One long sentence</p>" s)
(:p :class "foo" "Visit " (:a :href "http://www.cliki.net/" "CLiki"))
    => (write-string "<p class='foo'>Visit <a href='http://www.cliki.net/'>CLiki</a></p>" s)
ルール2.5
属性の別の書き方

バージョン0.4.0から、あなたはLHTMLのように、タグとすべての属性/値のペアを、追加のリストに括ったシンタックスを使える。

((:p) "Paragraph") => (write-string "<p>Paragraph</p>" s)
((:p :class "foo") "Paragraph") => (write-string "<p class='foo'>Paragraph</p>" s)
((:p :class "foo" :name "humpty-dumpty") "One" " " "long" " " "sentence")
    => (write-string "<p class='foo' name='humpty-dumpty'>One long sentence</p>" s)
((:p :class "foo") "Visit " ((:a :href "http://www.cliki.net/") "CLiki"))
    => (write-string "<p class='foo'>Visit <a href='http://www.cliki.net/'>CLiki</a></p>" s)

ちょっと複雑な例。

* (defun checkbox (stream name checked &optional value)
    (with-html-output (stream)
      (:input :type "checkbox" :name name :checked checked :value value)))

CHECKBOX
* (with-output-to-string (s) (checkbox s "foo" t))

 "<input type='checkbox' name='foo' checked='checked' />"
* (with-output-to-string (s) (checkbox s "foo" nil))

"<input type='checkbox' name='foo' />"
* (with-output-to-string (s) (checkbox s "foo" nil "bar"))

"<input type='checkbox' name='foo' value='bar' />"
* (with-output-to-string (s) (checkbox s "foo" t "bar"))

"<input type='checkbox' name='foo' checked='checked' value='bar' />"
ルール3
キーワードのみもタグに

キーワードのみがあれば、それはキーワードのみを含むリストと同じに取り扱われる。

:hr => (write-string "<hr />" s)
ルール4
特別扱いされるシンボル

文字列でもキーワードでも、キーワードではじまるリストでもないフォームは、次の変換を除いてそのまま残される。

str
文字列化

(str form1 form*)

のようなフォームは、

(let ((result form1)) (when result (princ result s)))

へと置換される。(form1より後のフォームはすべて無視される)

        (loop for i below 10 do (str i)) => 
        (loop for i below 10 do
           (let ((#:result i))
             (when <a href='#:result (princ '>:result (princ </a>:result *standard-output*))))

fmt
フォーマット

(fmt form*)
<||
というフォームは、
>||
(format s form*)
<||
というフォームに置換される。
>||
(loop for i below 10 do (fmt "~R" i)) => (loop for i below 10 do (format s "~R" i))

esc
エスケープ
(esc form1 form*)
というフォームは、
(let ((result form1)) (when result (write-string (escape-string result s))))
というフォームに置換される。
(htm form*)
というフォームは、それぞれのフォームが今記述しているtransformation rulesに従う

(loop for i below 100 do (htm (:b "foo") :br))
    => (loop for i below 100 do (progn (write-string "<b>foo</b><br />" s)))
ルール5
あとはそのまま

これで全てである。CL-WHOは、HTMLXHTMLについて何も知らないことに注意すべし。換言すれば、タグや属性名を許されない名前にミスタイプしてもチェックされない。CL-WHOはあなたが:hrの代わりに:foobarを使っても何も気にしない。


The CL-WHO dictionary

CL-WHOは次のシンボルをエクスポートしている。

[Macro] with-html-output (var &optional stream &key prologue indent) declaration* form* => result*

CL-WHOの主要なマクロである。これはそのbodySyntax and Semanticsで記述されたtransformation rulesにしたがって、生成した結果をvarおよびstreamで示されるストリームに出力する形式に変換する。streamNILならば、varがすでにストリームに束縛されていると仮定する。そうでなければ、varは実行時に評価されたstreamフォームの結果に束縛される。prologueは文字列でなければならず(もしくは、デフォルトでそうなっているが、空文字列を表すNILでなければならず)、それはマクロのボディからストリームに送られる最初のものであることが保証されている。もし prologueT なら、 prologue 文字列の値は*PROLOGUE* の値となる。CL-WHO は通常、帯域幅をセーブするため一切の不要な空白を入れないようにする。しかしながら、 indent が真ならば、改行が挿入され、ネストされたタグは適切にインデントする。もし indent の値が整数ならば、初めのインデントの深さとして捉えられる。もし整数でなければ、0を意味する。(しかし、インデントは生成されるHTMLのセマンティクスを変えてしまう可能性があることに注意されたい。 例えばPRETEXTAREAのケース、そして、場合によっては追加の空白がテーブルのレイアウトを買えてしまうこともある)。result*forms によって返される値です。

キーワード引数 prologueindent はマクロ展開時に使われることに注意されたい。

    * (with-html-output (*standard-output* nil :prologue t)
        (:html (:body "Not much there"))
        (values))
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html><body>Not much there</body></html>
    * (with-html-output (*standard-output*)
        (:html (:body :bgcolor "white"
             "Not much there"))
        (values))
    <html><body bgcolor='white'>Not much there</body></html>
    * (with-html-output (*standard-output* nil :prologue t :indent t)
        (:html (:body :bgcolor "white"
                 "Not much there"))
        (values))
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    <html>
      <body bgcolor='white'>
        Not much there
      </body>
    </html>

[Macro] with-html-output-to-string (var &optional string-form &key element-type prologue indent) declaration* form* => result*


WITH-HTML-OUTPUT まわりの薄いラッパーである.実際、あんまり薄いので、説明するのに一番の方法は、その定義を見せることだろう。

(defmacro with-html-output-to-string ((var &optional string-form
                                           &key (element-type 'character)
                                                prologue
                                                indent)
                                      &body body)
  "Transform the enclosed BODY consisting of HTML as s-expressions
into Lisp code which creates the corresponding HTML as a string."
  `(with-output-to-string (,var ,string-form :elementy-type ,element-type)
    (with-html-output (,var nil :prologue ,prologue :indent ,indent)
      ,@body)))


このマクロの結果は WITH-OUTPUT-TO-STRING の振る舞いによることに注意されたい。


[Macro] show-html-expansion (var &optional stream &key prologue indent) declaration* form* =>

このマクロはデバッグのためのものである。このマクロは、*STANDARD-OUTPUT*に同じ引数で起動された WITH-HTML-OUTPUT によって生成されるコードを印字する。

* (show-html-expansion (s)
    (:html
     (:body :bgcolor "white"
      (:table
       (:tr
        (dotimes (i 5)
          (htm (:td :align "left"
                (str i)))))))))
(LET ((S S))
  (PROGN
    (WRITE-STRING
      "<html><body bgcolor='white'><table><tr>" S)
    (DOTIMES (I 5)
      (PROGN
        (WRITE-STRING "<td align='left'>" S)
        (PRINC I S)
        (WRITE-STRING "</td>" S)))
    (WRITE-STRING "</tr></table></body></html>" S)))

[Special variable] *attribute-quote-char*

この文字は属性を構築するときに使うクォート文字として使われる。シングルクォート文字#\'がデフォルトになる。他に唯一意味をなす文字はダブルクォート文字#\"である。


[Special variable] *prologue*

これは WITH-HTML-OUTPUT に与えられたprologueキーワード引数がTであったときに印字されるプロローグ文字列である。HTML-MODEをセットする時には変更されたい*4。初期値は次のとおり。

    "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">"


[Special variable] *html-empty-tag-aware-p*

strictXMLジェネレータとしてCL-WHOを使いたい時、これをNILにセットされたい。そうでなければ、CL-WHO*HTML-EMPTY-TAGS* に列挙された空タグは、 (XHTML mode) あるいは(SGML mode) と記述される。その他のすべてのタグは、と出力される。この変数の初期値はTである。


[Special variable] *html-empty-tags*

このHTMLのタグのリストは空タグとして出力されるべきタグのリストである。 *HTML-EMPTY-TAG-AWARE-P* を見よ。この変数の初期値は次のリストである。

    (:area :atop :audioscope :base :basefont :br :choose :col :frame
     :hr :img :input :isindex :keygen :left :limittext :link :meta
     :nextid :of :over :param :range :right :spacer :spot :tab :wbr)

[Special variable] *downcase-tokens-p*

もしこの変数の値が NIL ならば、タグもしくは属性名を表しているキーワードシンボルは自動的にダウンケースされない。
これは大文字・小文字の区別に敏感なXMLを出力する時に便利である。デフォルトは T である。


[Symbol] fmt
[Symbol] htm
[Symbol] str


これらはいかなる束縛も関連付けられていないただのシンボルである。これらがエクスポートされている唯一の理由は、 Syntax and Semantics で記述された変換中で特別な意味を持っているからである。



[Accessor] html-mode => mode
[Accessor] (setf (html-mode) mode)


関数HTML-MODEは、現在のHTML生成モードを返す。そのデフォルトはXHTMLのために:XML である。あなたはこのモードを

(SETF (HTML-MODE) :SGML)

を使って XML以前のHTMLモードに変更できる。

もしモードをSGML HTMLにセットしたならば、 *prologue*doctype stringHTML 4.01 transitionalにセットせよ。


    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">


SGML HTMLにおけるコード生成は、 XHTML とは少し違う -- 空要素を /> で終わる必要がない他、空属性が許される。


[Function] escape-string string &key test => escaped-string

この関数は文字列stringを受け付け、testが真を返す文字を文字符号で置き換える。HTML-MODE:SGMLのとき、古いクライアントの機能を考慮して、数値実体参照は16進数ではなく10進数で行われる。testは1引数の関数でなければならず、それは文字を受け取りgeneralized boolean を返さねばならない。そのデフォルト値は *ESCAPE-CHAR-P* である。Syntax and SemanticsESC ショートカットについても見よ。

    * (escape-string "<H&#252;hner> 'na&#239;ve'")
    "&lt;H&#xFC;hner&gt; &#x27;na&#xEF;ve&#x27;"
    * (with-html-output-to-string (s)
        (:b (esc "<H&#252;hner> 'na&#239;ve'")))
    "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\"<b>&lt;H&#xFC;hner&gt; &#x27;na&#xEF;ve&#x27;</b>"

[Function] escape-char character &key test => escaped-string

この関数は、文字列ではなく文字を受け取る他は、ESCAPED-STRINGと同じように振る舞う。


[Special variable] *escape-char-p*

これは ESCAPE-CHAR へのtest キーワード引数のデフォルト値である。この変数のデフォルト値は以下のとおり。

    #'(lambda (char)
        (or (find char "<>&'\"")
            (> (char-code char) 127)))


[Function] escape-string-minimal string => escaped-string
[Function] escape-string-minimal-plus-quotes string => escaped-string
[Function] escape-string-iso-8859-1 string => escaped-string
[Function] escape-string-iso-8859 string => escaped-string
[Function] escape-string-all string => escaped-string
[Function] escape-char-minimal character => escaped-string
[Function] escape-char-minimal-plus-quotes character => escaped-string
[Function] escape-char-iso-8859-1 character => escaped-string
[Function] escape-char-all character => escaped-string

これらは ESCAPE-STRING および ESCAPE-CHAR の上に作られた便利関数である。文字列関数は次のものと同じように定義されている。

    (defun escape-string-minimal (string)
      "Escape only #\<, #\>, and #\& in STRING."
      (escape-string string :test #'(lambda (char) (find char "<>&"))))

    (defun escape-string-minimal-plus-quotes (string)
      "Like ESCAPE-STRING-MINIMAL but also escapes quotes."
      (escape-string string :test #'(lambda (char) (find char "<>&'\""))))

    (defun escape-string-iso-8859-1 (string)
      "Escapes all characters in STRING which aren't defined in ISO-8859-1."
      (escape-string string :test #'(lambda (char)
                                      (or (find char "<>&'\"")
                                          (> (char-code char) 255)))))

    (defun escape-string-iso-8859 (string)
      "Identical to ESCAPE-STRING-ISO-8859-1.  Kept for backward compatibility."
      (escape-string-iso-8859-1 string))

    (defun escape-string-all (string)
      "Escapes all characters in STRING which aren't in the 7-bit ASCII
    character set."
      (escape-string string :test #'(lambda (char)
                                      (or (find char "<>&'\"")
                                          (> (char-code char) 127)))))


文字関数も同じように定義されている。


[Function] conc &rest string-list => string


すべての引数(文字列でなければならない)を一つの文字列に連結するユーティリティ関数。主に属性値とともに使われることを意図している。

    * (conc "This" " " "is" " " "a" " " "sentence")
    "This is a sentence"
    * (with-html-output-to-string (s)
        (:div :style (conc "padding:"
                           (format nil "~A" (+ 3 2)))
         "Foobar"))
    "<div style='padding:5'>Foobar</div>"


[Generic Function] convert-tag-to-string-list tag attr-list body body-fn => strings-or-forms


この関数はCL-WHOのいくつかの内部シンボルを、ユーザがその振る舞いを変更できるように晒す。この関数はタグが処理される時にはいつでも呼び出され、関連する文字列あるいはLISPフォームのリストを返さねばならない。いくつかののタグをユーザ自身で処理するためにこの総称関数を特殊化することができる。

tagは外部タグに名前をつけるためのキーワードシンボルであり、attr-listはその連想リストを表す連想リスト(そのCAR部は属性名でありキーワード、CDR部はその値)、bodyはタグの要素、そしてbody-fnはその要素をさらに処理するためにに適用されるべき関数である。もちろん、もしあなたが独自のメソッドを定義したならば、body-fnを無視したければ無視できる。

これは単純なサンプルである。

    * (defmethod convert-tag-to-string-list ((tag (eql :red)) attr-list body body-fn)
        (declare (ignore attr-list))
        (nconc (cons "<font color='red'>" (funcall body-fn body)) (list "</font>"))) 
    ; Compiling LAMBDA (PCL::.PV-CELL. PCL::.NEXT-METHOD-CALL. TAG ATTR-LIST BODY BODY-FN): 
    ; Compiling Top-Level Form: 

    #<STANDARD-METHOD CONVERT-TAG-TO-STRING-LIST ((EQL :RED) T T T) {582B268D}>
    * (with-html-output (*standard-output*)
        (:red (:b "Bold and red")) 
        (values))
    <font color='red'><b>Bold and red</b></font>
    * (show-html-expansion (s)
        (:red :style "spiffy" (if (foo) (htm "Attributes are ignored")))) 

    (LET ((S S))
      (PROGN
       NIL
       (WRITE-STRING "<font color='red'>" S)
       (IF (FOO) (PROGN (WRITE-STRING "Attributes are ignored" S)))
       (WRITE-STRING "</font>" S)))
    * (defmethod convert-tag-to-string-list ((tag (eql :table)) attr-list body body-fn)
        (cond ((cdr (assoc :simple attr-list))
               (nconc (cons "<table"
                            (convert-attributes (remove :simple attr-list :key #'car)))
                      (list ">")
                      (loop for row in body
                            collect "<tr>"
                            nconc (loop for col in row
                                        collect "<td>"
                                        when (constantp col)
                                          collect (format nil "~A" col)
                                        else 
                                          collect col
                                        collect "</td>")
                            collect "</tr>")
                      (list "</table>")))
              (t 

*1:訳があやしい。原文は、"In other words, the code generated by the CL-WHO macros will usually be a sequence of WRITE-STRING forms for constant parts of the output interspersed with arbitrary code inserted by the user of the macro." @goodmadさんに指摘を受け多少修正。2012/07/15

*2:赤くないが、=>のこと

*3:翻訳が怪しい。原文は、"If it is T and HTML-MODE is :XML (default) the attribute's value will be the attribute's name (following XHTML convention to denote attributes which don't have a value in HTML). "

*4:変更される、かもしれない。原文は"Gets changed when you set HTML-MODE.

Alexandriaのマニュアル(ドラフト版)を訳したよ

CLikiの推奨ライブラリでもあり、(http://www.cliki.net/Current%20recommended%20libraries)、モダンなCommon Lispの標準でもある(http://modern-cl.blogspot.jp/2011/04/4-common-lisp.html)ユーティリティライブラリ、Alexandriaのマニュアルを訳したよ。

注意

  • 訳したのはドラフト版のマニュアル(http://common-lisp.net/project/alexandria/draft/alexandria.htm)だよ。
    • 半分以上は動作未検証だよ
    • 訳がわかんなかったとこだけ動かしたよ
  • 割とてきとーな訳だよ。
    • 訳すのがめんどくさくなって書き下したところは原文を一緒に載せているよ
    • ??ってかかれてるのはわかんなかった所、訳が怪しいところだよ
    • ツッコミ歓迎だよ。
  • 島は移動しないよ
  • 眠いよ

Contents

1 Hash Tables
2 Data and Control Flow
3 Conses
4 Sequences
5 Macro Writing
6 Symbols
7 Arrays
8 Types
9 Numbers

Hash Tables(ハッシュテーブル)

— Function: ensure-gethash key hash-table &optional default

gethash のように動作する。しかし keyhash-table に見つけられなければ、値を返す前に key に対応する値として default をハッシュテーブルに登録し、それを返す。

第二の返り値は、値がすでにハッシュテーブルにあれば真となる。

— Function: copy-hash-table table &key key test size rehash-size rehash-threshold

table と同じキーと値を持ったハッシュテーブルのコピーを返す。コピーは、キーワード引数で上書きされたもの以外は、オリジナルと同じ属性を持つ。

オリジナルの値に対しては、新しいハッシュテーブルに登録される前に、key が呼び出される。 key はデフォルトでは cl:identity であるので、浅いコピーがデフォルトでは返る。

— Function: maphash-keys function table

maphash のようなものだが、 table ハッシュテーブルのキーに対してだけfunction を呼び出す。

— Function: maphash-values function table

maphash のようなものだが、 table ハッシュテーブルの値に対してだけfunction を呼び出す。

— Function: hash-table-keys table

ハッシュテーブル table のキーのリストを返す

— Function: hash-table-values table

ハッシュテーブル table の値のリストを返す

— Function: hash-table-alist table

ハッシュテーブル table と同じキーと値を持つ連想リストを返す。

— Function: hash-table-plist table

ハッシュテーブル table と同じキーと値を持つ属性リストを返す。

— Function: alist-hash-table alist &rest hash-table-initargs

連想リスト alist とおなじキーと値を持つハッシュテーブルを返す。ハッシュテーブルは hash-table-initargs を使って初期化される。

— Function: plist-hash-table plist &rest hash-table-initargs

属性リスト plist とおなじキーと値を持つハッシュテーブルを返す。ハッシュテーブルは hash-table-initargs を使って初期化される。

Data and Control Flow(データと制御構造)

— Macro: define-constant name initial-value &key test documentation

name によって名付けられるグローバル変数が、初期値 initial-value の評価結果にtestのもとで等しい値として一定になることを保証する。testは関数指示子であり、デフォルトではeqlである。もしドキュメンテーションが与えられていれば、それは定数のドキュメンテーション文字列となる。

もしnameがすでに定数でない変数として束縛されていればエラーを通知する。

もしnameがすでに評価の結果testのもとで等価でない定数に束縛されていればエラーを通知する。

訳注:defconstant は、再定義してもエラーを投げず、 warning を発するだけである。

— Macro: destructuring-case keyform &body clauses

destructuring-case, -ccase そして -ecase は、 casedestructuring-bind のコンビネーションである。keyform はコンスに評価されねばならない。

Clauses は次のようなフォームである。

            ((CASE-KEYS . DESTRUCTURING-LAMBDA-LIST) FORM*)

case-keyskeycar 部と、 case, ccase, ecase のようにマッチした節が選択され、FORMdestructuring-lambda-list にしたがって分解・束縛された状態で実行さる。

Example:

           (defun dcase (x)
             (destructuring-case x
               ((:foo a b)
                (format nil "foo: ~S, ~S" a b))
               ((:bar &key a b)
                (format nil "bar, ~S, ~S" a b))
               (((:alt1 :alt2) a)
                (format nil "alt: ~S" a))
               ((t &rest rest)
                (format nil "unknown: ~S" rest))))
     
            (dcase (list :foo 1 2))        ; => "foo: 1, 2"
            (dcase (list :bar :a 1 :b 2))  ; => "bar: 1, 2"
            (dcase (list :alt1 1))         ; => "alt: 1"
            (dcase (list :alt2 2))         ; => "alt: 2"
            (dcase (list :quux 1 2 3))     ; => "unknown: 1, 2, 3"
     
           (defun decase (x)
             (destructuring-case x
               ((:foo a b)
                (format nil "foo: ~S, ~S" a b))
               ((:bar &key a b)
                (format nil "bar, ~S, ~S" a b))
               (((:alt1 :alt2) a)
                (format nil "alt: ~S" a))))
     
            (decase (list :foo 1 2))        ; => "foo: 1, 2"
            (decase (list :bar :a 1 :b 2))  ; => "bar: 1, 2"
            (decase (list :alt1 1))         ; => "alt: 1"
            (decase (list :alt2 2))         ; => "alt: 2"
            (decase (list :quux 1 2 3))     ; =| error

— Macro: multiple-value-prog2 first-form second-form &body forms

prog2の多値版。フォームを順に評価し、2番めのフォームから返される値を返す。フォームから返される値が多値ならば、多値を返す。

Evaluates first-form, then second-form, and then forms. Yields as its value all the value returned by second-form.

訳注: prog
prog, prog*
let, block, tagbody を合わせて使える邪悪なマクロ。Lispを手続き型に変える程度の能力を持つ。かつて広く使われていたという。
progn
おなじみのアレ。多値を返すことができる。
prog1, prog2
progn と同じだが、値を返すのが最後のフォームではなく、1番目(2番め)のフォームになっているのが特徴。多値を返せないという欠点を持つ。
multiple-value-prog1
多値を返せるようになったprog1.

— Macro: named-lambda name lambda-list &body body

名前付きの lambda再帰ができる。展開形としては、 labels で作った関数を返すクロージャである。

Expands into a lambda-expression within whose body name denotes the corresponding function.

— Macro: nth-value-or nth-value &body forms

複数の、多値を返す関数に対しての OR 。forms を順に評価していき、返された多値の第 nth-value 要素が nil でなければ、そのフォームから返された多値を返す。このとき、残りのフォームは評価されない(短絡)。 nth-value に0を指定すれば or と同じに使える。ただ、orは元々多値を返せるっぽい。対応するのは多値に対してでリストにではないことに注意。

Evaluates form arguments one at a time, until the nth-value returned by one of the forms is true. It then returns all the values returned by evaluating that form. If none of the forms return a true nth value, this form returns nil.

— Macro: if-let bindings &body (then-form &optional else-form)

新たな変数束縛を作り、 then-formelse-form を条件付き実行する。else-form はデフォルトでは nil である。

bindings は次のような1つの束縛フォームであるか、

           (variable initial-form)

束縛フォームのリストでなければならない。

           ((variable-1 initial-form-1)
            (variable-2 initial-form-2)
            ...
            (variable-n initial-form-n))

初期化フォームが仕様順に評価され、変数が対応する値に束縛される。

もしすべての変数が真値に束縛されていれば then-form が、そうでなければ else-form が束縛のもとで実行される。

— Macro: when-let bindings &body forms

新しい束縛を作り、 forms を条件付き実行する。bindings は次のような1つの束縛フォームであるか、

           (variable initial-form)

あるいは次のような束縛フォームのリストでなければならない。

           ((variable-1 initial-form-1)
            (variable-2 initial-form-2)
            ...
            (variable-n initial-form-n))

すべての 初期化フォームが仕様順に実行され、対応する変数に束縛される。

もしすべての変数が真値に束縛されていれば、formsが暗黙のprognとして実行される。

— Macro: when-let* bindings &body forms

新たな変数束縛を作り、 then-formelse-form を条件付き実行する。

bindings は次のような1つの束縛フォームであるか、

           (variable initial-form)


あるいは次のような束縛フォームのリストでなければならない。

           ((variable-1 initial-form-1)
            (variable-2 initial-form-2)
            ...
            (variable-n initial-form-n))


それぞれの初期化フォームが順に実行され、対応する変数に束縛される。初期化フォーム式は when-let* による、より前の変数を参照できる。

when-let* の実行は初期化フォームのいずれかがnilに評価された時ただちに停止する。もしすべての初期化フォームが真値に評価されたならば、 forms が暗黙のprognの内で実行される。

— Macro: switch whole (object &key test key) &body clauses

はじめにマッチした節を評価し、その値を返すか、もしどのキーもマッチしなければ、評価して default の値を返す。

— Macro: cswitch whole (object &key test key) &body clauses

switchと同様に動作するが、どのキーもマッチしなければ継続可能なエラーが発生する。

— Macro: eswitch whole (object &key test key) &body clauses

switchと同様に動作するが、どのキーもマッチしなければエラーが発生する。

— Macro: whichever &rest possibilities env

possibilities のうちランダムに選ばれた1つだけを評価する。

— Macro: xor &rest datums

引数を左から右に一度に評価する。1つより多くの引数が真値に評価されたとき、残りの引数の評価は直ちに中止され、nil nil が返される。もしたった1つだけの引数が真値に評価されたとき、その値が第一の返り値、第二の返り値として t が返る。もしすべての引数が nil に評価されたとき、第一の返り値として nil 、第二の返り値として t が返る。

— Function: disjoin predicate &rest more-predicates

predicate および more-predicates を順に適用していき、はじめて真を返した述語の第一返り値を残りの述語を適応せずにただちに返すか、いずれも真を返さなければ nil を返す関数を返す。

— Function: conjoin predicate &rest more-predicates

predicate および more-predicates を順に適用していき、いずれかでも偽を返したときは残りの述語を適応せずにただちにnilを返すか、いずれも真を返せば最後の述語の第一返り値を返す関数を返す。

— Function: compose function &rest more-functions

合成関数を返す。引数に対して、最も右の関数から適用し、第一返り値を左の関数に渡す。

Returns a function composed of function and more-functions that applies its arguments to to each in turn, starting from the rightmost of more-functions, and then calling the next one with the primary value of the last.

— Function: ensure-function function-designator

function-designator で示される関数を返す。もし function-designator が関数ならばそのまま返し、そうでなければ fdefinition を呼び出す。

訳注: function-designator はシンボルあるいは carsetf であるリストでなければならない。

訳注2: fdefinitionsymbol-definition とだいたい同じらしいが違いがよくわからない。

— Function: multiple-value-compose function &rest more-functions

compose のような合成関数を返す。ただし、多値が返された時、その値はすべて次の関数への引数となる(つまり mutiple-value-call っぽく動作する)。

Returns a function composed of function and more-functions that applies its arguments to each in turn, starting from the rightmost of more-functions, and then calling the next one with all the return values of the last.

— Function: curry function &rest arguments

関数を返す。返された関数は任意の引数を取り、 arguments の後にその引数をつけくわえて function を適用する。

Returns a function that applies arguments and the arguments it is called with to function.

— Function: rcurry function &rest arguments

関数を返す。返された関数は任意の引数を取り、 arguments の前にその引数をつけくわえて function を適用する。

Returns a function that applies the arguments it is called with and arguments to function.

Conses(コンス)

— Type: proper-list

属性リストのための型指示子。 satisfies 型指定子として実装されているので、パフォーマンスが要求される使用には推奨できない。主な使い道は type-error の時の expected type に使われる型指示子。

— Type: circular-list

循環リストのための型指示子。 satisfies 型指定子として実装されているので、パフォーマンスが要求される使用には推奨できない。主な使い道は type-error の時の expected type に使われる型指示子。

— Macro: appendf place &rest lists env

append のためのモディファイマクロ。 lists を第一引数で示される場所に連結する。

— Macro: nconcf place &rest lists env

nconc のためのモディファイマクロ。 lists を第一引数で示される場所に破壊的に連結する。

— Macro: remove-from-plistf place &rest keys env

remove-from-plist のためのモディファイマクロ。

— Macro: delete-from-plistf place &rest keys env

delete-from-plist のためのモディファイマクロ。

— Macro: reversef place env

reverse のためのモディファイマクロ。 場所 place に蓄えられたリストをコピーして逆順にし、同じ場所に記録する。

— Macro: nreversef place env

nreverse のためのモディファイマクロ。 place にあるリストを破壊的に逆順にし、同じ場所に記録する。

— Macro: unionf place list &rest args env

union のためのモディファイマクロ。第一引数で示される場所 place のリストと list の和集合を、place にセーブする。

— Macro: nunionf place list &rest args env

nunion のためのモディファイマクロ。第一引数で示される場所 place のリストと、 list の和集合を、place にセーブする。 place 以外の引数は変更される可能性がある。

— Macro: doplist (key val plist &optional values) &body body

plist の要素の上で繰り返しを行う。 bodytagbodyのようなもので、宣言で始まってよい。returnが繰り返しを中断させるために使える。returnが使われなければ、valuesが返される。

— Function: circular-list-p object

もし object が循環リストなら t そうでなければ nil を返す。

— Function: circular-tree-p object

もし object が循環ツリーなら t そうでなければ nil を返す。

— Function: proper-list-p object

もし object が真のリスト(ドットリストでないリスト)なら真を返す。

— Function: alist-plist alist

連想リスト alist に含まれるキー及び値を同じ順番で含む属性リストを返す。

— Function: plist-alist plist

属性リスト plist に含まれるキー及び値を同じ順番で含む連想リストを返す。

— Function: circular-list &rest elements

elements の循環リストを作る。

— Function: make-circular-list length &key initial-element

初期値 initial-element を含む、長さ length の循環リストを作る。

— Function: ensure-car thing

もし thing がコンスならそのcar部が、そうでなければ thing それ自体を返す。

— Function: ensure-cons cons

cons がコンスならば、それ自体が、そうでなければcons がcar部に、nilがcdr部にあるコンスを返す。

— Function: ensure-list list

もしも list がリストなら、そのまま返す。そうでなければ、listを要素とするリストにして返す。

— Function: flatten tree

tree を順に渡り歩き、 nil でない葉の集合であるリストを返す。

— Function: lastcar list

list の最後の要素を返す。 list が真のリストでなければエラーになる。

— Function: (setf lastcar)

最後の要素にセットする。真のリストでなければエラーになる。

— Function: proper-list-length list

list の長さを返すが真のリストでなければエラーを発生させる

訳注: length でも同様のエラーが発生するが、(clispの場合)CLtL2にはそうせねばならない旨は書かれていないようだ。(2012/07/02)

— Function: mappend function &rest lists

それぞれの lists のそれぞれの要素に function を適用し、結果リストをすべて append する。function はリストを返さなければならない。

— Function: map-product function list &rest more-lists

list から要素を1つ、そして more-lists からも1つずつ要素を取り出す、その取り出し方の組み合わせごとに関数を呼び出した結果を含むリストを返す。別の言い方をすると、listmore-listの直積集合にfunctionを適用した結果のリストを返す。

Returns a list containing the results of calling function with one argument from list, and one from each of more-lists for each combination of arguments. In other words, returns the product of list and more-lists using function.

Example:

           (map-product 'list '(1 2) '(3 4) '(5 6))
            => ((1 3 5) (1 3 6) (1 4 5) (1 4 6)
                (2 3 5) (2 3 6) (2 4 5) (2 4 6))


— Function: remove-from-plist plist &rest keys

属性リスト plist と同じキーと値を持つ属性リストを返す。
ただし、keys に含まれるキーとそれに対応する値は含まれない。
返される属性リストは plist と構造を共有するが、
plist は破壊的に変更されることはない。
キーは EQ で比較される。

— Function: delete-from-plist plist &rest keys

remove-from-plist と同じように動作するが、属性リスト plist は破壊的に変更される可能性がある。

— Function: set-equal list1 list2 &key test key

list1 のすべての要素が list2 のいずこかに含まれ、 list2 のすべての要素が list1 のいずこかに含まれれば真、そうでなければ偽を返す。

— Function: setp object &key test key

object が集合を表すリストなら真、さもなければnilを返す。集合を表すリストとは、その要素が key および test のもとでユニークであるリストである。

Sequences(シーケンス)

— Type: proper-sequence

真のシーケンスのための型指定子である。真のシーケンスとは、真のリストとリストでないシーケンスである。

— Macro: deletef place item &rest remove-keywords env

delete のためのモディファイマクロ。第一引数で示される場所 place に、itemplace そして remove のためのを引数を渡して、 delete を呼び出した結果をセットする。

— Macro: removef place item &rest remove-keywords env

remove のためのモディファイマクロ。第一引数で示される場所 place に、itemplace そして remove のためのを引数を渡して、 remove を呼び出した結果をセットする。

— Function: rotate sequence &optional n

sequencen だけ回転させた、 sequence と同じ型のシーケンスを返す。nが正なら末尾の要素が先頭に、nが負なら先頭の要素が末尾に移動する。sequence はプロパーなシーケンスでなければならない。n は整数でなければならず、デフォルトは1である。

もし n の絶対値が sequence の長さより大きければ、その結果はn が (* (signum n) (mod n (length sequence))) で呼び出された時とおなじになる。

注意:オリジナルの sequence は破壊的に変更される可能性があり、また返り値と構造を共有する可能性がある。

— Function: shuffle sequence &key start end

start から end までの範囲をランダムに並び替えられた sequence を返す。変更されたシーケンスは記憶領域を元のシーケンスと共有する可能性がある。もし sequence が真のリストでなければエラーを通知する。

訳注:

(let ((str (list 'a 'b 'c)))
  (alexandria:shuffle str)
  str)

とかするとランダムな順序のリストが返される超パワー。破壊的だと思ったほうがよさそう(つまりこの使い方は正しくない)。

— Function: random-elt sequence &key start end

sequencestart から end までの要素の1つをランダムで返す。真のシーケンスでなければエラーを通知する。

— Function: emptyp sequence

sequence が空なら t を返す。もしシーケンスでなければエラーを通知する。

— Function: sequence-of-length-p sequence length

sequence の長さがlength ならば真を返す。sequence がシーケンスでなければエラーを通知する。循環リストに対しては偽を返す。

— Function: length= &rest sequences

任意のシーケンス、あるいは整数を任意の順で取る。もしすべてのシーケンスの長さと整数が等しければ真値を返す。ヒント:もし第一引数が整数リテラルならばより効率的なコードに展開するコンパイラマクロがある。

— Function: copy-sequence type sequence

type 型の新たなシーケンスを返す。それはsequenceと等しい要素を持つ。

— Function: first-elt sequence

シーケンスの第一要素を返す。もし sequence がシーケンスでなかったり、空であればエラーを通知する。

— Function: (setf first-elt)

シーケンスの第一要素に値をセットする。もし sequence が空だったり、シーケンスでなかったり、シーケンスに入れられないならばエラーを通知する。

— Function: last-elt sequence

シーケンスの最後の要素を返す。 sequence が真のシーケンスでなかったり、空のリストであれば type-error を返す。

— Function: (setf last-elt)

シーケンスの最後の要素に値をセットする。もし sequence が空だったり、シーケンスでなかったり、シーケンスに入れられないならばエラーを通知する。

— Function: starts-with object sequence &key test key

もし sequence の第一要素が objectEQL ならば真を返す。もし sequence がシーケンスでなかったり、空なら nil を返す。

— Function: starts-with-subseq prefix sequence &rest args &key return-suffix &allow-other-keys

sequence のはじめの要素がそれぞれ test のもとで prefix の各要素と等しいかを判定する。もし return-suffixt ならば、関数は第二の返り値として prefix と一致した後の配列を返す。

— Function: ends-with object sequence &key test key

sequence の最後の要素が objectEQL であれば真を返す。もし sequence がシーケンスでなかったり、空なら nil を返す。もし sequence がプロパーなシーケンスでなければエラーを通知する。

— Function: ends-with-subseq suffix sequence &key test

sequencesuffix で終わるかテストする。言い換えれば、 sequence の最後の (length SUFFIX) 個の要素が suffixEQUAL なら真を返す。

— Function: map-combinations function sequence &key start end length copy

startend によって区切られた サブシーケンスから構成しうる、長さ length の組み合わせのそれぞれに対して function を呼び出す。start はデフォルトで0、 end はデフォルトで sequence の長さ、length はデフォルトでサブシーケンスの長さである。(だから、lengthが指定されなければ、可能な組み合わせはたった一つ、サブシーケンスそのものである)もし copy が真なら(それがデフォルトだが)、それぞれの組み合わせのために新たな記憶領域が確保される。

If copy is false all combinations are eq to each other, in which case consequences are specified if a combination is modified by function.

— Function: map-derangements function sequence &key start end copy

start から end で定義される sequence のサブシーケンスの、それぞれの撹乱順列にたいして function が呼ばれる。撹乱順列 とは、 sequence のいずれの要素も元の位置にない順列のこと。sequence は変更されないが、それぞれの撹乱順列は互いに EQ である。撹乱順列あるいは sequence を、呼び出された関数が変更した場合の動作は未定義。

— Function: map-permutations function sequence &key start end length copy

start から end で定義される sequence のサブシーケンスから構成可能な長さ length の順列に対してfunction を呼び出す。start はデフォルトで0、 end はデフォルトで sequence の長さ、 length はデフォルトでサブシーケンスの長さである。

Macro Writing(マクロを書く)

— Macro: once-only specs &body forms

それぞれの初期化フォームが一度だけ評価されることを保証するため、spaces で指定されたシンボルを一時変数に再束縛して forms を評価する。

spaces は再束縛される変数の名前か、次のようなフォームでなければならない。

            (symbol initform)

spaces 中の裸のシンボルは下と等しい

            (symbol symbol)

Example:

            (defmacro cons1 (x) (once-only (x) `(cons ,x ,x)))
            (let ((y 0)) (cons1 (incf y))) => (1 . 1)

— Macro: with-gensyms names &body forms

forms の周りで、names 中のシンボルをそれぞれユニークなシンボルに束縛する。names の各要素は再束縛される変数の名前か、次のようなフォームでなければならない。

           (symbol string-designator)


names 中の裸のシンボルは下と等しい

           (symbol symbol)

文字列指定子は、ユニークな名前を生成するときに使われる gensym への引数になる。

— Macro: with-unique-names names &body forms

with-gensyms の別名。

— Function: featurep feature-expression

もし引数が *features* の状態とマッチすればt、そうでなければnilを返す。feature-expressionはリーダマクロ#+と#-に与えられるアトムあるいはリストならなんでもよい。

— Function: parse-body body &key documentation whole

?? body をパースし、残りのフォーム、宣言、ドキュメント文字列を多値で返す。ドキュメント文字列は、documentationが真であるときだけ返される。この辺から分からん。

Parses body into (values remaining-forms declarations doc-string). Documentation strings are recognized only if documentation is true. Syntax errors in body are signalled and whole is used in the signal arguments when given.

— Function: parse-ordinary-lambda-list lambda-list &key normalize allow-specializers normalize-optional normalize-keyword normalize-auxilary

?? さっぱり分からん

Parses an ordinary lambda-list, returning as multiple values:

1. Required parameters.

2. Optional parameter specifications, normalized into form:
(name init suppliedp)

3. Name of the rest parameter, or nil.

4. Keyword parameter specifications, normalized into form:
((keyword-name name) init suppliedp)

5. Boolean indicating &allow-other-keys presence.

6. &aux parameter specifications, normalized into form
(name init).

Signals a program-error is the lambda-list is malformed.

Symbols(シンボル)

— Function: ensure-symbol name &optional package

パッケージ指示子packageからアクセスできる、nameで表されるシンボルを返す。もしsymbolがすでにpackageからアクセスできるようになっていなければ
シンボルはそのパッケージにインターンされる。第二返り値は、シンボルがパッケージにあったかどうかを反映しており、internの第二返り値に相当する。

Example:

            (ensure-symbol :cons :cl) => cl:cons, :external

— Function: format-symbol package control &rest arguments

formatに文字列指定子controlと引数argumentsが渡されたかのように文字列を構成し、packagenilならアンインターンドシンボルを返し、tならカレントパッケージにインターン、そうでなければpackageで示されるパッケージにインターンする。

— Function: make-keyword name

name で示される文字列をキーワードパッケージにインターンする

— Function: make-gensym name

name が非負の整数であれば、それを使ってgensymを呼ぶ。そうでなければnameは文字列指定子でなければならない。この場合、指定された文字列が引数としてgensymに渡される。

— Function: make-gensym-list length &optional x

length 要素のシンボルのリストを返す。その要素は、それぞれ第二引数(デフォルトでは"G")を引数に make-gensym を呼び出した結果である。

— Function: symbolicate &rest things

?? 文字列あるいはシンボルの名前を連結して、カレントパッケージにシンボルを登録する。

Concatenate together the names of some strings and symbols, producing a symbol in the current package.


Arrays(配列)

— Type: array-index

?? 長さlengthの配列のインデックス(0以上length未満の整数)を表す型指定子。lengthのデフォルトは、array-dimension-limit

Type designator for an index into array of length: an integer between 0 (inclusive) and length (exclusive). length defaults to array-dimension-limit.

— Type: array-length

?? 長さlengthの配列の次元(0以上length以下の整数)を表す型指定子。lengthのデフォルトは、array-dimension-limit.

Type designator for a dimension of an array of length: an integer between 0 (inclusive) and length (inclusive). length defaults to array-dimension-limit.

— Function: copy-array array &key element-type fill-pointer adjustable

?? キーワード引数で上書きされない限り、同じフィルポインタと連結可能性を持つ array の非表示コピーを返す。効率は実装の adjust-array に依存する。大体の場合、特別な目的を持つコピー関数の方が効率がよい。

Returns an undisplaced copy of array, with same fill-pointer and adjustability (if any) as the original, unless overridden by the keyword arguments. Performance depends on efficiency of general adjust-array in the host lisp -- for most cases a special purpose copying function is likely to perform better.

Types(型)

— Type: string-designator

文字列指定子型。文字列指定子とは、文字列、シンボル、あるいは文字のこと。

— Macro: coercef place type-spec env

coerce のモディファイマクロ

— Function: of-type type

一引数の関数を返す。その関数は、引数が type 型ならば真を返す。

— Function: type= type1 type2

?? 第一返り値がtならば、type1type2は同じ型である。そして、第二返り値が真ならば、型の等しさは確定的に明らかである。第一返り値がnilで第二返り値がtならば型は等しくない。

Returns a primary value of t is type1 and type2 are the same type, and a secondary value that is true is the type equality could be reliably determined: primary value of nil and secondary value of t indicates that the types are not equivalent.

Numbers(数)

— Macro: maxf place &rest numbers env

max のモディファイマクロ。第一引数で示される場所に、その場所にあった値および numbers の最大値をセットする。

— Macro: minf place &rest numbers env

min のモディファイマクロ。第一引数で示される場所に、その場所にあった値および numbers の最小値をセットする。

— Function: binomial-coefficient n k

binomial coefficient (二項係数) n k 、あるいは n 個から k 個を選ぶ組み合わせの数。nk 以上でなければならない。


— Function: count-permutations n &optional k

長さ n の順列から長さ k の順列を作る組み合わせの数を返す。kのデフォルトはnである。

— Function: clamp number min max

numbermin 以上 max 以下にとどめる。 numbermin 以下ならば min を、max 以上なら max を、そうでなければ number を返す。

— Function: lerp v a b

ab の間の、補完係数 v を使った線形補間を返す。

— Function: factorial n

非負の整数 n の階乗を返す。

— Function: subfactorial n

非負の整数 nsubfactorial を返す。

note: subfactorial とは、 n の階乗と自然対数の除を超えない最大の自然数

— Function: gaussian-random &optional min max

2つの正規乱数を第一・第二返り値として返す。 mixmax によって制限をつけることもできる。正規乱数は 0.0d0 を平均とするの正規分布に従う。

— Function: iota n &key start step

?? start から始まり、 step ずつ増加する長さ n のリストを返す。 start のデフォルトは0、stepのデフォルトは1である。

Return a list of n numbers, starting from start (with numeric contagion from step applied), each consequtive number being the sum of the previous one and step. start defaults to 0 and step to 1.

Examples:

            (iota 4)                      => (0 1 2 3 4)
            (iota 3 :start 1 :step 1.0)   => (1.0 2.0 3.0)
            (iota 3 :start -1 :step -1/2) => (-1 -3/2 -2)

— Function: map-iota function n &key start step

?? start から始まり、 step ずつ増加する長さ n のリストの各要素に function を適用した結果のを返す。 start のデフォルトは0、stepのデフォルトは1である。

Calls function with n numbers, starting from start (with numeric contagion from step applied), each consequtive number being the sum of the previous one and step. start defaults to 0 and step to 1. Returns n.

Examples:

            (map-iota #'print 3 :start 1 :step 1.0) => 3
              ;;; 1.0
              ;;; 2.0
              ;;; 3.0

— Function: mean sample

sample の mean(平均)を返す。 sample は数字のシーケンスでなければならない。

— Function: median sample

sample の median(中央値)を返す。 sample は数値のシーケンスでなければならない。

— Function: variance sample &key biased

sample のvariance(分散)。 biased が真ならば biased variance を返す(これがデフォルト)。
biased が偽ならば unbiased estimator of variance を返す。
sample は数字のシーケンスでなければならない。

— Function: standard-deviation sample &key biased

sample の standard deviation (標準偏差)。 biased が真ならば biased standard deviation を返す(これがデフォルト)。biased が偽ならば、 unbiased estimator for variance の平方根を返す。(これと unbiased estimator for standard deviation は同じではない)sample は数字のシーケンスでなければならない。

パスネーム可搬ライブラリCL-FAD

Cliki の current recommended libraries*1 のひとつである、パスネーム可搬ライブラリ、CL-FAD の説明を訳した。てきとー訳なので、間違っていたら許してほしい。ツッコミ歓迎。

元はこちら。http://weitz.de/cl-fad/

別の方の紹介。http://sakito.jp/cl/cl-fad.html

おおきな訳注:パスネーム、ディレクトリ形式、真の名前について

パスネームとは、CLにおいてファイルの名前を表すためのクラスであり、DIRECTORY, NAME, TYPE, DEVICE, HOST, VERSIONの6つのスロットを持つ。PATHNAME 関数を呼び出すことで、文字列からパスネームのインスタンスを作ることができる。

DIRECTORY にはファイルを含むディレクトリがリストで保存され、 NAME にはファイル名、 TYPE には拡張子、 HOST か DEVICE にはドライブレターが入る(Windowsの場合)。かつてバージョンを管理していたファイルシステムがあったため VERSION スロットが存在するが、今では滅多に使われない(らしい)。これにより、ファイルシステムに依存しない形でファイルのパスを表現できる。

シンボリックリンクの解決のようなファイルシステムレベルでの変換をすべて実行したパスネームを『真の名前』という。

ディレクトリには2つの表現方法がある。WindowsおよびUNIXシステムにおいて、ディレクトリは特殊なファイルでもあるので、もっとも内側のディレクトリの名前を NAME に入れることもできるし、NAME に何も入れずにすべて DIRECTORY スロットに入れることもできる。前者の形式をファイル形式と言い、後者の形式をディレクトリ形式という。これらが混在すると困ったことになる。

CL-FADでは、ファイル形式とディレクトリ形式を相互に変換し、またあるパスネームが指すディレクトリが存在するかどうかをテストする述語を提供することで、この問題を回避する。

ここから翻訳

Abstract

CL-FAD ( ファイルとディレクトリ "Files and Directories" のための ) は Common Lisp の標準のパスネーム関数の上の薄いレイヤである。これは、近年の Windows, OS X, Linux などのCL実装の間の統一を行うことを目的としている。

ほとんどのコードは Peter Seibel によって、彼の本である Practical Common Lisp (実践 Common Lisp) のために書かれた。*2

CL-FAD は BSD-style ライセンスと共に配布されているので、基本的に、あなたはこれを使って好きなことができる。

Contents
  1. Download and installation
  2. Supported Lisp implementations
  3. The CL-FAD dictionary
    1. directory-pathname-p
    2. pathname-as-directory
    3. pathname-as-file
    4. file-exists-p
    5. directory-exists-p
    6. list-directory
    7. walk-directory
    8. delete-directory-and-files
    9. copy-file
    10. copy-stream
  4. Acknowledgements
Download and installation

(訳注:省略されました。quicklispで入るよ)

Supported Lisp implementations

現在、次のCommon Lisp実装がサポートされている。

CL-FADを他のプラットフォームでも動作させられるパッチがあれば喜んで受け取る。


The CL-FAD dictionary

[Function] DIRECTORY-PATHNAME-P pathspec => generalized-boolean
pathspec(パスネーム指示子)がディレクトリ形式でないときは nil を返し、そうでなければ pathspec を返す。 pathspec が指しているファイルやディレクトリが本当に存在しているかは問題にしない。
[Function] PATHNAME-AS-DIRECTORY pathspec => pathname
ワイルドでないパスネーム指示子である pathspec をディレクトリ形式に変換する。すなわち、 DIRECTORY-PATHNAME-P が 真を返すようなパスネームを返す。
[Function] PATHNAME-AS-FILE pathspec => pathname
ワイルドでないパスネーム指示子 pathspec をファイル形式に変換する。すなわち、 DIRECTORY-PATHNAME-P が NIL を返すようなパスネームを返す。
[Function] FILE-EXISTS-P pathspec => generalized-boolean
パスネーム指示子 pathspec で名付けられるファイルが本当に存在しているのか試し、存在すれば真の名前を返し、さもなければ NIL を返す。真の名前は canonical なフォームで返される。すなわち、ディレクトリを指す真の名前は、 PATHNAME-AS-DIRECTORY を使ったかのようにtディレクトリフォームで返される。
[Function] DIRECTORY-EXISTS-P pathspec => generalized-boolean
パスネーム指示子 pathspec で名付けられるファイルが本当に存在しているのか、そしてそれがディレクトリであるかを試す。もしそうならば真の名前を返し、そうでなければ NIL を返す。真の名前は PATHNAME-AS-DIRECTORY を使ったようなディレクトリフォームで返される。
[Function] LIST-DIRECTORY dirname => list
ワイルドでないパスネーム dirname で示されるディレクトリに含まれるすべてのファイルに対応する真の名前のパスネームのリストを返す。サブディレクトリのパスネームはディレクトリフォームで返される。PATHNAME-AS-DIRECTORY を見よ。
[Function] WALK-DIRECTORY dirname fn &key directories if-does-not-exist test => |
関数指示子 fn によって示される関数を、ワイルドでないパスネーム指示子 dirname およびそのサブディレクトリに含まれるすべてのファイルに対して再帰的に適用する。fn は関数 test が真を返すファイルにだけ適用される(デフォルトでは test は常に真を返す関数である)。もし directories が NIL でなければ、 fn と test はディレクトリにも適用される。もし directories が :DEPTH-FIRST ならば、 fn はディレクトリの中身に対して最初に適用される。もし directories が :BREADTH-FIRST であり test が NIL を返す場合、ディレクトリの中身はスキップされる。if-does-not-exist は :ERROR あるいは :IGNORE でなければならない。デフォルトでは :ERROR であり、ディレクトリ dirname が存在しない時エラーを通知する。
[Function] DELETE-DIRECTORY-AND-FILES dirname &key if-does-not-exist => |
ワイルドでないパスネーム dirname で示されるディレクトリのファイル及びディレクトリを dirname そのものを含めて再帰的に消去する。 if-does-not-exist は、 :ERROR あるいは :IGNOREでなければならず、デフォルトである :ERROR は irname が存在しない時エラーを通知する。
[Function] COPY-FILE from to &key overwrite => |
ワイルドでないパスネーム指示子 from が示すファイルをワイルドでないパスネーム指示子 to が示すファイルへとコピーする。もし overwrite が真なら(デフォルトではNIL)、 to で示されるファイルが存在していても上書きする。
[Function] COPY-STREAM from to &optional checkp => |
from (ストリーム)から to (これもストリーム)へと from の終了にたどり着くまでコピーされる。どちらのストリームも bivalent でないかぎり同じ element type でなければならない。もし checkp が真ならば(これがデフォルト)、 element type が異なっているとき、エラーを通知する。

Acknowledgements

このライブラリのオリジナルのコードは Peter Seibel によって、彼の著書 Practical Common Lisp のために書かれた。私はこれにいくらか付け加え、 Windowsでも、具体的にはCCLで動くようにした。 OpenMCL, ECL, ABCL, MCL, and Scieneer CL で動くようにパッチを送ってくれた James Bielman, Maciek Pasternacki, Jack D. Unrue, Gary King, and Douglas Crosher に感謝する。

Rubyメタプログラミング読んで思いついたたわごと

きっかけ

メタプログラミングRuby』を読み直す。再読して気づいたけど、書いてあることの大半は、Rubyのオブジェクトモデルの解説だ。言い換えると、Rubyにおいてクラスやメソッド、インスタンス変数がどんなふうに作られて、実際にはどこに置かれているのか、メソッドが呼び出された時裏でどのような処理が動いているのか、などについてだ。もちろんというか、RubyVMがどう動いてるかとかそういうレベルの話じゃなくて、Rubyソースコードがどのようなモデルに基づいて解釈されるかってことだ、。

それをまとめると、次のようになるようだ。

  • Rubyのクラス定義やメソッド定義は、宣言じゃない。コンパイル時じゃなくて実行時に『実行されて』クラスやメソッドを定義する。
  • Rubyの構文には、それと等価なメソッド呼び出しが存在する
    • defによる関数定義と等価なdefine_methodメソッド
    • classによるクラス定義と等価なClass.newメソッド
    • @a = 1のような代入や参照と等価なメソッド
    • メソッド呼び出しと等価なsendメソッド
    • その他

これらを上手く活用することで、実行時にクラスを生成したり、メソッドを生成したり、インスタンス変数を作り出したり、動的に呼び出すメソッドを変えたりすることができる。

メタ度が足りねーなと一瞬思った

Rubyがクラスやメソッドを動的に定義していることを考えれば、定義のための構文相当のメソッドがあることは自然なことであり、なーんだそれを書き換えるだけかー、と私は思った。

そういうんじゃない色々とアレなメソッド、evalについても触れられているのはわずかで、そこで私は「構文木を操作できるくらいじゃないとメタじゃねーな」とかLispかぶれなことを考えた。

でも、リストで表現された構文木を操作できたらメタプログラミングなのかっていうと、それはメタプログラミングのひとつではあれ、メタプログラミングのすべてではない。そもそもメタプログラミングとは何か。ウィキペディアにはこうある。

メタプログラミング (metaprogramming) とはプログラミング技法の一種で、ロジックを直接コーディングするのではなく、あるパターンをもったロジックを生成する高位ロジックによってプログラミングを行う方法、またその高位ロジックを定義する方法のこと。主に対象言語に埋め込まれたマクロ言語によって行われる。

うむ。つまりこういうことだろ?

メタプログラミング=コード生成

あるパターンに従うコード群があった場合、実際にコードを書くのではなく、略記法を考える。そして、その略記法を実際のコードに機械的に変換することで、開発効率を上げたり、意図するところを明確にすることができる。

これを実装レベルで考えると、あるパターンに従うコードの略記法としてのDSLをパースし、パターンに従うコードを自動生成するということだ。

すなわちメタプログラミングの考え方はコード生成から説明できる。

だよね?

     /: : : : : __: :/: : ::/: : ://: : :/l::|: : :i: :l: : :ヽ: : :丶: : 丶ヾ    ___
      /;,, : : : //::/: : 7l,;:≠-::/: : / .l::|: : :l: :|;,,;!: : :!l: : :i: : : :|: : ::、  /     ヽ
     /ヽヽ: ://: :!:,X~::|: /;,,;,/: :/  リ!: ::/ノ  l`ヽl !: : |: : : :l: :l: リ / そ そ お \
    /: : ヽヾ/: : l/::l |/|||llllヾ,、  / |: :/ , -==、 l\:::|: : : :|i: | /   う う  前  |
 .   /: : : //ヾ ; :|!: イ、||ll|||||::||    ノノ  イ|||||||ヾ、 |: ::|!: : イ: ::|/   な 思 が
    /: : ://: : :ヽソ::ヽl |{ i||ll"ン    ´   i| l|||l"l `|: /|: : /'!/l     ん う
  ∠: : : ~: : : : : : : :丶ゝ-―-      ,  ー=z_ソ   |/ ハメ;, :: ::|.   だ ん
    i|::ハ: : : : : : : : : : : 、ヘヘヘヘ     、  ヘヘヘヘヘ /: : : : : \,|.   ろ な
    |!l |: : : : : : : : :、: ::\    、-―-,      / : : :丶;,,;,:ミヽ   う  ら
      丶: :ハ、lヽ: :ヽ: : ::\__  `~ "      /: : ト; lヽ)   ゝ
        レ `| `、l`、>=ニ´        ,  _´ : :} `   /
          ,,、r"^~´"''''"t-`r、 _  -、 ´ヽノ \ノ   /    お ・
        ,;'~  _r-- 、__     ~f、_>'、_         |  で  前 ・
       f~  ,;"     ~"t___    ミ、 ^'t         |  は  ん ・
       ,"  ,~         ヾ~'-、__ ミ_ξ丶     |  な  中 ・
      ;'  ,イ ..          ヽ_   ヾ、0ヽ丶    l         /
      ( ;":: |: :: ..          .`,   ヾ 丶 !    \____/
      ;;;; :: 入:: :: ::      l`ー-、   )l   ヾ 丶
      "~、ソ:: :い:: :     \_  ノ ,    ヾ 丶

その発想で考えるとどういうことになるかというと。

たとえば、インスタンス変数に対する型通りのアクセサメソッドを定義することを考えてみる。アクセサメソッドの定義は次のような決まりきった形になる。

class Moge
  def hoge()
    @hoge
  end

  def hoge=(v)
    @hoge=v
  end
end

こんなものはいちいちタイプするより、自動生成したい。あるクラスの中で、対象にしたいインスタンス変数を列挙しておくと、そこに勝手にアクセサメソッドを挿入してくれるようなコードの前処理器を作りたいとしよう。

じゃあ、まずはパーサを作ろう(おもむろにlexとyaccを取り出して)

ないないそれはない。もっとすでにある解析器でもって一瞬で解析できる方法にするのがいい。

たとえばコードに埋め込むだけなら一行コメントを正規表現で抜き出せばいい。もっと複雑な場合でも、コメントに埋め込んだXMLなりJSONなりYAMLなり、そういうもので表現すればすでにあるライブラリで解析できる。

今回の場合は、コメントに埋め込むようにしてみようか。行頭に'#gen-accessor 'というパターンがあったら、その後に続く識別子へのアクセサを埋め込むことにしよう。識別子は1つ以上のスペースあるいはタブで区切られる。また、Ruby識別子に不適切な名前だった場合は無視される。

この働きをするフィルタスクリプトRubyで簡単に書ける。*1

#!/usr/bin/ruby
# -*- encoding: utf-8 -*-

# 1行ごとに順に処理していく。
while line = ARGF.gets do
  line.chomp!

  unless /^#gen_accessor (.*$)/
    # パターンに合致しなければそのまま出力する。
    puts line
  else
    # 合致した場合はどうするか
    # 解析器で記法を解析し、生成器でコードを生成する
    # ...というほど大した感じでもないが

    $1.splite(/\s+/).each do |property| # ここがコードの解析器。単にsplitするだけ
      if property =~ /[a-z]\w*/ then
        # ここがコード生成器。文字列に変数を埋め込むだけ。簡単だ。
        puts <<-EOS
          # 生成されるゲッターメソッド
          def #{property}()
            @#{property}
          end

          # 生成されるセッターメソッド
          def #{property}=(v)
            @#{property}=v
          end
        EOS
      end
    end
  end
end

これでコードを前処理すればアクセサを自動定義できる。やったねたえちゃん! 自動化できるよ!

もっとRubyのコードに

ところで、実行時に定義できるような言語、たとえばRubyなら、define_methodとinstance_variable_[set|get]を使ってメソッドを定義できる。つまり、コード生成器はいらない。

さらに、その場合はインスタンス変数のリストもコメントではなく、単なるRubyの文字列のリストとして記述できる。これで読取り器もいらない。

class Class
  # メソッドの定義をするのはクラス内。
  # 自動定義するためには、オブジェクトとしてのクラスにメソッドを定義してやればいい
  # 複数の文字列を受け取るメソッドにしよう
  def define_accessor *plist
    plist.each do |property|
      self.class_eval do
        # ゲッターメソッドの動的定義
        define_method property do
          return instance_variable_get("@#{property}")
        end

        # セッターメソッドの動的定義
        define_method "#{property}=" do |v|
          return instance_variable_set("@#{property}", v)
        end
      end
    end
  end
end

class SampleClass
  # 使ってみる
  define_accessor 'hoge', 'fuga', 'piyo'
end

s = SampleClass.new

s.hoge = 'hoge'
puts s.hoge
#=> hoge
#=> nil
これってattr_accessorじゃないですかー! やだー!
   / ̄ ̄\
  / ノ  \ \
  |  (●)(●) |
 . |  (__人__)  |   ごめんね
   |   ` ⌒´  ノ
 .  |         }
 .  ヽ        }
    ヽ     ノ        \
    /    く  \        \
    |     \   \         \
     |    |ヽ、二⌒)、          \
何が言いたいかというと

Rubyの動的な機能を利用することでコードジェネレータを簡単に作るのと等しい効果を得ることができる。

所感

メタプログラミングってやっぱコード生成じゃないかな……限定的なジェネレータだけでも十分に効果がある。とはいえ、コードを書き出すスクリプトを書くのは微妙にめんどいし、生成ルールが増えてくるとフィルタを正しい順番で適用するだけで手に負えなくなってくるだろう。

ところが、プログラミング言語自体の力を使うことで、コード生成はぐっと簡単になる。プログラミング言語処理系にはコードを解析する機能も、クラスやメソッドを定義する機能もある。それを再利用してやることで書くべきコードは減る。

それがコンパイラ内にあるとき、それを最利用することは難しいが、実行時に利用できるなら、コードを生成するのと実質的に同じことができる。さらに、文法もプログラミング言語のものを流用することで、パーサを作成する必要もない。そういう使い方ができる言語はメタプログラミングに向いているといえるだろう。

結局のところRubyメタプログラミングに向いている。Ruby強い。

ところでメタプログラミングといえばLISP

LISPのマクロはコード生成により近い。だが、Rubyのコード生成をするよりずっと楽だ。

  • リストであればread関数で読み込める
  • 生成するのは文字列ではなくリストであればよい

readで読んだリスト状のデータに基づいて、リストを生成する。簡単だ。しかもLISPの場合、それをいちいち外部のプリプロセッサとして書かなくても、マクロという形で提供されている。

さらに、LISPにはシンボルオブジェクトがある。Rubyでは文字列やシンボルであったものを、識別子のように書くことで書きやすさ・元の文法との統一感は加速した。Rubyはこれができないのが不満だよね。Rubyにもあればいいのに、S式。

最後に

  • メタプログラミングは魔術のようなもので、冒涜的で名状しがたいことができるようになる
  • メタプログラミングがなんであるかの解釈論なんて書くよりコードを書いたほうがいい気がした
  • 島は移動しない

*1:インデントの考慮? 文字列の中にパターンがあった場合の考慮? ねえよそんなもん。

ぼっちの私が勉強したいって思う時はいつかって

なんで勉強するんですか?

なぜ勉強するんですか? という質問に対して、理由を並べて、だから勉強するんです、というのはあまり好きじゃない。

その理由、というか、嫌だな、と思うのは2パターンあって、1つは「あなたの得に(損に)なるんだから勉強しなさい」というやつ。

将来のためとかわかんねーし

子供のときは、そんなこと言われてもリアリティがない。想像がついてこない。「将来お金持ちになれない」とか、「学校に行かないと信用されない」とか言っても、実際にお金を稼いだこともなければ、見知らぬ他人に信用を求めたこともない子供には、「お金を稼がなければ」とか「信用を得る材料が欲しい」とか感じる、具体的なシチュエーションも出てこない。

だから、「なんだかわからんが勉強は大切」「なんだかわからんが勉強しないと」さらには「勉強できないやつはダメ」というような、教条主義に陥ってしまう。そこから生まれるのは不安であって、やる気じゃない。

と思う。

お前がそう言うなら美しいんだろう、おまえの世界はな

もう1つは「世界はこんなにも美しい」ってやつで、これも、じっさい「こんなにも美しい世界」ってやつを感じたことがないと通じない、相当にふわっとした言葉だと思う。

「1位で切るテープはたまらない。だから陸上やろうぜ」とか、「絶妙の連携で胸熱。だからサッカーやろうぜ」とか言われて、運動音痴で引っ込み思案な奴がスポーツ始めますかというのと似ている。確かに楽しいかもしれないけど、やってみないと分かんないんだよな。

「なんで勉強すんの?」とか言ってるときは、その「試しにやってみる」っていうはじめのやる気すらない状態なのに、「やったら楽しいよ」とか知らねえよって。

勉強したい時

で、そういうこと考えてるひねくれ満点な私がどういうときに「勉強してえな」ってしみじみ思うかというと、自分がすごいなって思ってる人に、自分の知らないことを「知ってるよね?」って言われた時。まあ、言われなくても、「当然知ってるよね?」って扱われた時。

それも、「まあ愚民はともかく教養人は知ってて当然の基礎知識だよね(ドヤッ」とか、「知らないんですかwwwwwもっと勉強してくださいよwwwwwww」とか、そういうバカにする感じじゃなくて、「お前なら俺の言いたいこと分かるだろ?」とか、「俺こういうの面白いと思うんだけどお前どう思うよ」とか、そういう期待を持って言われた、と感じる時。

そういう時って、「説教する相手」とか「管理する相手」じゃなくて、「話し相手」とか「仲間」って認められた感と同時に、「俺はこの人の期待に全然答えられてねえ」っていう悔しさ、申し訳なさを感じる時。これはもう勉強するしかないだろと感じる。

そこでなんか適当な感じで知ったかぶったりとか、生半可な知識で適当なことぶちあげて、「ああ、こいつ……知らないのか……」みたいな顔されたら悔しいわ寂しいわ情けないわで、もうね、なんで勉強して来なかったんだろうって思うね。ごめんね。

その他

次点で、あれこれ試すのが楽しい時。ゲーム感覚。

やってもやっても全然分かんねえ頭ごちゃごちゃで砂を噛んでる気分、って時はなんかもう駄目だ。なにやっても頭入んない。

逆にだんだんわかってきた時とか、簡単すぎつまんねえなって思ってるくらいで、若干難しい問題が出た時とかテンション上がる。

東方Projectやっててしみじみ感じたけどステップアップ大事。超大事。パターン1つ1つ作っていって最終的にボスを突破するとか、基本のムーブ1個1個練習してコンボ使えるようになるとか、

そんな積み上げの心を僕は東方Projectで学びました。

そういう心を教えてくれる東方Projectはとてもいいゲームだと思いました(作文)。

LISPは実はあんまり難しくないんじゃないか。まあ個人の感想でね?

嘘だ。

これは『実践 Common LISP』読んだ上でのわかりづらかったポイントの個人的なメモ

  1. 括弧
  2. 再帰
  3. シンボル
括弧

どうせエディタが対応とってくれる。

嘘だっ!

括弧だらけだと言われるのは、条件分岐も繰り返しも配列も連想配列もパッケージ定義もクラス定義もメソッド定義もみんなリストの形をしたコードで、違いがわからないからだろうと思う。それらが見た目に目立って異なるシンタックスで表現される言語だと、コードのパターンが見つけやすく、それを取っ掛かりに全体像を把握できる気がするけど、LISPはどこを切り取ってもリスト、リスト、リストで取っ掛かりがない。

まあ、それがLISPの強みではあるのだけれど。

基本的な制御構造やデータ構造で、LISPにあるものはRubyにもだいたいある。当たり前といえば当たり前。わかってみればそこはごく普通の言語なのだった。まる。

再帰

慣れの問題が半分くらい、何を書きたいかちゃんと理解してるかが半分くらいあると思った。未熟なのでしょうがない。熟してくると慣れの比率が上がるのだろう。

ついついループで考えてしまうのを抑制するのは難しい。でもループに必ず再帰が必要なわけではなく、do系もloopマクロもある。また、再帰的でないようなコードを書いても別に誰にも怒られはしない。そういうわけだから、これから毎日setfしようぜ?

traceを使うと関数の呼び出され方が見えるようになるらしい。これで再帰呼び出しの様子が見られるよ! やったねたえちゃん!


シンボル

LISPは、そのコードがデータと同じ方法で記述される。

それを、はじめのうちは、『LISPの構文とリストの構文に類似性がある』とか、『S式はLISPのプログラムともリストとも評価される』みたいに、LISPプログラムとリストを切り分けるような考え方をしていたんだけど、どうもそういう考え方をしているとわけがわからなくなる。シンボルが変数として扱われたり、そうじゃなかったりすることで混乱して、頭がおかしくなって死ぬ。

LISPのプログラムは単なるリストでしかない。Rubyで例えると、

[ :format, :t, "Hello World!" ]

みたいに書かれているのと変わらない。 :format は変数じゃなくてただのオブジェクトで、属性として名前なんかを持っている。それが、評価器に入れられた時にたまたま変数とか関数名として振る舞うことになるってだけ。そう考えると、シンボルが評価されたりされなかったりするスペシャルフォームとかマクロとかで混乱しない。と思う。

たぶん。この解釈であってるのか、自信はない。

そしてこれは今もってLISPわけわかんねえ……ぱねえ……と思うこと

処理系依存の仕様

パスネームとかファイルシステムとのやり取りとか。マニュアルが厚くなるな……。

汎用言語であること

LISPにはまだ見ぬ機能が山ほどあるらしい(伝聞)。高レベルな処理といえばマクロだけれど、かなり低レベルな処理も仕様の範囲内で可能であるらしい(伝聞)。

へっぽこプログラマにはちょっと手に負えない。

Hyperspecの日本語訳がないこと

英語に関しては甘え禁止らしいので言い訳できない。

LISPerの存在

存在がハードル。覇気によって心の弱いアマグラマは死ぬ。

文法知識だけあっても結局実装したい対象への理解がなければよいものは作れないこと

泣きたい。

Rubyで同図像性っぽいことをする

思いついた

あるプログラミング言語が、そのプログラミング言語が操作するデータを用いて記述される場合、その言語は同図像性を持つ、というらしい。

へっぽこRuby使いである私は、それがRubyでもできないかな? というか、できるんじゃないかな? と思った。

実際にやってみた。

ソースの表現

LISPはシンボルとリストによりソースを表現するが、Rubyの場合、それに相当するのは配列とキーワードシンボルなので、それを使うとこんなふうにソースを書くことになる。

# puts(1, 2, sum(3, 4)) に相当
source = <

読取器と評価器

ソースを読んでRubyのデータ構造に変換する読み取り器、それを評価する評価器は、こんな感じに書ける。

def reader source
  eval(source)
end

def evaluate sexp
  self.send( sexp.map{|exp|
               if exp.is_a?(Array) then
                 evaluate(exp)
               else exp
               end
             })
end

変数? なにそれおいしいの?

やってることは簡単。

  1. 配列を次のルールで写像:配列以外ならそのまま返し、配列なら再帰的に評価
  2. self.sendに写像した配列を渡すことでメソッドを呼び出す。英語で言うと動的ディスパッチ

次のように sum メソッドを定義しておけば、[:sum, 3, 4]がsum(3,4)扱いなので、正しく動く。

def sum(*args)
  args.inject(:+)
end

マクロ

さらに、読み取った後のソースはRubyの配列なので、Rubyの配列操作メソッドで好きなように改変できる。これを利用するとプログラムを書くプログラムが書ける。英語で言うとマクロ。

マクロ展開関数を次のように書いてみる。ここではsayというマクロが定義されている。これは、 [:say, arg, ...]を [:print, arg, ..., "\n"]に変換する。(まあ、そんなものは本当は関数で書けばいいんだけども)

def expand_macro(sexp)
  if sexp[0] == :say
    return [:print, *sexp[1..-2], "\n"]
  else
    return sexp
  end
end

評価機にマクロ展開ステップを追加してみる。

def evaluate sexp
  # 新たに追加されたマクロ展開のステップ
  expanded_sexp = expand_macro(sexp)

  self.send( *expanded_sexp.map{|exp|
               if exp.is_a?(Array) then
                 evaluate(exp)
               else exp
               end
             })
end

これで、次のソースが動く。

# puts(1, 2, sum(3, 4)) に相当
source = <

ソースの全体

def sum(*args)
  args.inject(:+)
end

def expand_macro(sexp)
  if sexp[0] == :say
    return [:print, *sexp[1..-2], "\n"]
  else
    return sexp
  end
end


source = <

やってみた感想

同図像性があるとマクロが使えて便利。というか、マクロを書くのに慣れ親しんだ配列のメソッドを使えて便利。

黒魔術っぽくも見えるが、『達人プログラマー』にも、擬似コードから実際のコードを生成するテクニックが載っていたし、黒魔術ではない。島は移動しない。

動的ディスパッチしてるだけで何も新しくないし黒くもないと思ったあなたは正しい。

キーワードシンボルのためのコロンや、要素を区切るためのカンマをいちいち書くのは実にめんどくさい。その点、シンボルもオブジェクトで、空白で区切りさえすればリストを書けるLISPは完璧で幸福な言語である。

readerメソッドをいじればリーダマクロが書ける。正規表現もあるしきっとなんとかなる。

Pythonでもできるかも。