LoginSignup
6
4

CommonLisp入門 兼 備忘録(ASDFもあるよ)

Last updated at Posted at 2023-03-31

前書き & Lispエイリアンが怖いので予防線を張る

とあるLispエイリアンから教わったことを書き綴ります。
私はもうCommonLispと関わらないかもしれませんが、この知識が消えてしまうのは非常にもったいないことなので。

なお私はCommonLispエンジョイ勢であり、Lisp戦闘力はたったの5しかない貧弱一般人です。
頻繁にLispを使っているLispガチ勢のエイリアンと比べるとマジで取るに足らない存在なので、厳密な言葉の意味が違ったり、誤ったことを言っているかもしれませんが、ご了承ください。

環境構築

Roswellをインストールしましょう。
これはJavaScriptでいうNode.jsみたいなものです。

ブラウザ上で動作確認したい人はWandboxというサイトがオススメです。

以下、Roswellをインストールしたとして話を進めます。
(なお、私は基本的にUbuntuで動作確認しています。Windowsで動かなくても許してね。)

余談

Roswellのインストール手順(古い)

昔の私がUbuntuで実行したコマンド。バージョンが古いので参考程度に。
なお、パスを通したりといったことはしなくてもRoswellを使えました。

curl -L https://github.com/roswell/roswell/releases/download/v21.10.14.111/roswell_21.10.14.111-1_amd64.deb --output roswell.deb \
&& sudo dpkg -i roswell.deb \
&& rm roswell.deb

うまくいけばホームディレクトリに.roswellフォルダができるはずです。

JSCL

JavaScriptで動くCommonLispです。
ただ、すべての機能が使えるわけではないのでお遊び程度に。

エディタ

私はVSCodeにCommonLispの拡張機能を入れてコーディングしています。

LispエイリアンはLEM一択でしょうが、こちらはEmacsの知識が必須なので初心者にはオススメしません。(本質的でないところで躓いてほしくない)

多分、LEMの由来はLisp Emacs Modeだと思います。

余談

Slime

調べものをしているとSlimeという単語を目にするかもしれません。
これはEmacsでCommonLispを実行するためのツールのことです。
エディタに関心がないなら気にしなくていいです。(本質的でないところで略)

LEM, Emacs

LispエイリアンはよくLEMとEmacsを押しています。
そんなにTUIがいいのか?時代遅れでは?と思ってしまいますが、
Emacs経由でLispにめり込んだ人も多そうなのでこうなっているのかもしれません。

個人的にはVSCodeのほうが好きです。
何故ならマウスが使えて、拡張機能を入れれば関数やマクロのヒントが簡単に参照でき、
なおかつリファレンスサイトであるCLHSへのリンクを出してくれるためです。

TUIに慣れている人は脳みそとキーボードが一体化しているのでLEMのほうが高速でプログラミングできます。
またLEMは簡単にS式を評価できる機能があるのもよい点でしょう。
でもやはり敷居が高いと思います。
Emacsの知識は必須であり、設定ファイルをちゃんと定義しないとロクに使えたものではないです。
まあ、設定ファイルを書き込むのも醍醐味だと感じる人も多そうですが。

EmacsとかLEMって複数ファイルの切り替えとかの操作が直感的に行えないし、コピペもなんかしにくいし、文字列の置換だったり矩形選択もVSCodeに比べて面倒に感じてしまう。単一のファイルを編集する程度ならまだいいけど、複数ファイルあるプログラムを編集したいと思えない。

その他

Portacleはportableとcl(CommonLisp)を掛け合わせたギャグです。
USBメモリで持ち運びできるIDEだからPortacleという名称なんでしょう。
またCommonLispのエイリアンは名前にclを入れたがる習性があります。

REPL

ros run

ros runコマンドでCommonLispのREPLが起動します。
関数の使い方や挙動を軽く調べたいときに重宝します。

抜けるときは(exit)と入力してください。
訳の分からんことを言われたら、(abort)と入力するかabortに割り当てられている数値を入力してください。
それでも意味が分からん~~~~~!!!!って状況になったら、Ctrl+Cをこれでもか!ってくらい連打してください。REPLが不貞腐れて終了します。

余談

簡単な計算などをして遊びましょう。

(+ 1 2 3)
(- 1 2 3)
(* 1 2 3)
(/ 10 3)
(format nil "~a 足す ~a は ~a" 12 30 (+ 12 30))

いちいち関数とかを事細やかに説明する気はないので、最低限の材料として書いときました。…

プログラムファイルの実行

hoge.lispというファイルにmain関数を書いた後

ros hoge.lisp

とすることでmain関数が実行されます。

REPLから

* (load "hoge.lisp")
* (main)

としても同様です。

サンプル:hoge.lisp
hoge.lisp
(defun main ()
  (format t "Hello, World~%"))

余談

拡張子をlispにしていますが、多分関係ないです。
CommonLispの構文で書かれているならtxtだろうが、tikuwaだろうが実行できます。
(少なくとも、エントリポイントとなるファイルならば)

言語仕様

CommonLispの言語仕様の勉強は以下のサイトを見るのがよいと思います。

特に、広井誠氏の「お気楽 Common Lisp プログラミング入門」はすごく分かりやすいです。
私がCommonLispでプログラミングするときはこのサイトと逆引きCommonLispとCLHSくらいしか参照しません。
本当に参考になりました。
というか、CommonLisp初学者はこのサイトだけ読めばいいのでは?という感じです。
かなり完璧な入門サイトなので、絶対に見たほうがいいです。

また逆引きCommonLispも神サイトですが、今はプレイバシーエラーが発生して見れません。

上記のサイト群を見れば「CommonLisp完全に理解した」となれます。

以下、自分の記憶引き出し用に雑な説明と思い出をダラダラを書いていきます。
そんなの見たくない人はここでブラバしてください。

値(その1)

;; 文字列
"hogehoge"

;; 文字
#\a ; 「a」の文字
#\A ; 「A」の文字
#\HIRAGANA_LETTER_A ; 「あ」の文字

;; 整数, 小数, 分数
123
1.2
4/6 ; 2/3に自動で変換してくれる。かしこい

;; 真偽値
t   ; 真
nil ; 偽(空のリストもnilと表現される)

;; リスト
'(1 2 3)
(list 1 2 3)

リストの前に'(シングルクォート)がついていますが、
これは実行するな。自分自身を評価するな。という意味です。
犬の命令風に言うと「待て」です。
CommonLispはカッコに囲まれていると先頭の値を関数やマクロと見なして評価する(多分)ので
'(シングルクォート)をつける必要があったんですね。
(厳密には'(シングルクォート)は何もしないという意味ではないかもしれませんが、実際にプログラミングするときに、この認識で特に困ったことがないです。)

(1 2 3) ; -> Error! 1という関数を実行しようとする
'(1 2 3) ; 何もしない

余談

CommonLispではほぼすべてがリストで表現されています。
関数の実行もリストです。

(+ 1 2) ; -> 3
(listp '(+ 1 2)) ; -> T

;; おまけ
;; evalで評価!
(eval '(+ 1 2)) ; -> 3

変数

グローバルな変数(スペシャル変数)の定義
(defparameter *hoge* 123)
グローバルな定数の定義
(defconstant +hoge+ 123)
局所的な変数の定義
(let ((hoge 123)
      (fuga 456)
      (piyo 789))
  (process1)
  (process2)
  (process3)
  ...)

;; let*はletと違い、変数定義の中で先に定義した変数が使える
(let* ((hoge 123)
       (fuga (+ hoge 111))
       (piyo (+ fuga 111)))
  (process1)
  (process2)
  (process3)
  ...)

なお、letの第一引数のカッコの数で構文エラーを起こすのは全Lisperが体験するはずです。

変数の値の変更
(setf hoge 123
      fuga 456
      piyo 789)

余談

defvar

スペシャル変数の定義方法としてdefvarがあります。
こちらは初期値を指定しないとnilが初期値として設定されます。

(defvar *hoge* 123) ; *hoge* = 123
(defvar *fuga*) ; *fuga* = nil

個人的に暗黙的に初期値が設定されるのはバグに繋がりやすいと考えているので私は使いません。

変数名

習慣としてスペシャル変数は*で、定数は+で囲みます(イヤーマフ)。
これは変数捕捉というLispの問題を解消するためのいにしえの習慣です。

具体的な命名ルールはこちら


変数名、関数名は結構自由に付けられます。
好き嫌いがあるでしょうけど、以下のような命名が許されるのは面白いところです。

;;;; 面白いと思う命名

;; 高さの半分アピール
(let ((height/2 (/ height 2)))
  ...)

;; 比率アピール
(let ((height/width (/ height width)))
  ...)

;; 比較した値アピール
(let ((height<width (< height width)))
  ...)

;; 123を足す関数アピール
(defun 123+ (num)
  (+ num 123))

;; 数値を文字列に変換する関数アピール
(defun num->str (num)
  (format nil "~a" num))

;; テスト番号4.2.3を実行する関数アピール
(defun test.4.2.3 ()
  ...)

真偽値を扱うシンボルの命名として末尾にpをつける慣習があります。
個人的に見にくいので嫌いですが、Lispエイリアンの琴線に触れるかもしれないのでOSSしたい場合は従ったほうが無難でしょう。

setq

CommonLispの調べものをしているとsetqで代入しているケースを見かけるかもしれません。
これはsetfの下位互換のようなものなので覚えなくていいです。

ちなみにsetfは左手だけで入力可能です。地味にありがたい。

また「setqで説得」はLisperなら知っておきたいギャグです。

それ以外にも「マンガで分かるLisp」の著者zick氏が考案したギャク
「carは左側通行、cdrは右側通行の乗り物」
「ティー(t)は煮る(nil)もの」
「eval(えばる)」
「condはまた今度」
「メリークLispマス」
は未だに私の心に残り続けています。

ちなみに、りこさんの腰の帯の結び目の配色はSBCLのロゴマークの配色と同じです。

defconstant

defconstantで定義できる定数はおそらくプリミティブな値(文字列、数値など)しか指定できないと思われます。

レキシカル変数とスペシャル変数が同名だとどうなる?

letで定義した変数が優先されます。
まあ、スペシャル変数は前述の命名規約で*で囲まれているので普通は気にしませんが。

既存の関数と同名の変数名を定義できる。

CommonLispでは既存の関数と同名の変数名を定義でき、なおかつ混同されません。(多分)
CommonLispにはlist関数があり、かつlistって変数名使いたいなーって場面が個人的にあるのでありがたいです。

コンスセル

あー、コンスセルの概念自体は各自でググってください。
別に理解する必要はないですし、意識することもないです。
(Lispエイリアンが怒りそうですが、アプリ開発程度でコンスセルがどうのこうの意識することはないです。リストの元ネタなんだなーって感じでしかないです。)

どうでもいいですが
「コンスセル」と「ランドセル」は発音が似ていると思います。

条件分岐

フツーのif
;; hogepがtなら実行
(when hogep
  (process1)
  (process2)
  ...)

;; hogepがnilなら実行
(unless hogep
  (process1)
  (process2)
  ...)
if-else
;; hogepがt   → t-processを実行
;; hogepがnil → nil-processを実行
(if hogep
    (t-process)
    (nil-process))

注意点として、CommonLispのifは分岐後にひとつのS式しか実行できません。
複数のS式を実行したい場合はprognを利用しましょう。

(if hogep
    (progn
      (t-process1)
      (t-process2)
      ...)
    (progn
      (nil-process1)
      (nil-process2)
      ...))

私はこの構文が好きではないので、初めにwhen,unless,condで条件分岐できないかを考えています。
もちろん、prognを使わずにきれいに記述できるなら普通にifを利用します。

以下の記事は全く面白くなく、役立つこともないので読まなくていいです。
思い出として貼っておきます。

どうでもいいけど、prognはifかマクロの戻り値でしか使った覚えがないです。

if-elseif-elseif-…-else
;; hogep, fugap, …と上から順に条件を評価し、それがtの場合にそこの処理を実行しcondを抜ける
;; 最後の条件をtとすることですべてに一致しなかったときに行う処理を記述できる
(cond (hogep
       (hoge-process1)
       (hoge-process2)
       ...)
      (fugap
       (fuga-process1)
       (fuga-process2)
       ...)
      (piyop
       (piyo-process1)
       (piyo-process2)
       ...)
      ...
      (t
       (t-process1)
       (t-process2)
       ...))

ちなみに同じ考え方でJavaScriptにswitch(true)イディオムというものがあります。

swich

caseを使いましょう。(雑)

余談

三項演算子

ifは三項演算子のように利用することができます。

(if t   123 456) ; -> 123
(if nil 123 456) ; -> 456
分岐判定箇所は真偽値である必要がない

when, unless, ifなどの分岐判定は値がnilかどうかだけ見ています。(多分)

(when "hoge" 123) ; -> 123
(when nil    123) ; -> NIL
(unless "hoge" 123) ; -> NIL
(unless nil    123) ; -> 123
(if "hoge" 123 456) ; -> 123
(if nil    123 456) ; -> 456

ちなみに空のリストはnilと同値なので注意?しましょう。
まあ、これはこれで再帰の終了判定などに使えて便利です。

(if '() 123 456) ; -> 456

;; おまけ
;; 0と""は真判定だよというお話
(if 0  123 456) ; -> 123
(if "" 123 456) ; -> 123
eq, eql, equal, equalpの使い分け

こちらの記事を読みましょう。

以下、自分の考え

eqは処理系によって挙動が異なるそうなので使わない。
(まあ処理系はSBCL一強状態だけど)
(OSSのコードは普通にeqが使われているけど)
(eqがデファクトスタンダードな気がするけど)

eqlはシンボルの同一性を調べるときに使う。
(eqとの違いが分からない。Googleはeql押し)

equalとequalpは判定がガバガバなので使わない。

数値の同値比較は=
文字列の同値比較はstring=
文字の同値比較はchar=
を使う。

equal、equalpでのガバガバ判定を利用したいときは、悔い改めて、判定する対象専用の比較関数を自作する。(Javaのequalsをオーバーライドする感じ)

というか、これを読みましょう。

Google Common Lisp スタイルガイド 日本語訳:同一性、等価性、比較


おまけ:参考になると思ったequalの使用例
SBCLよりマクロ展開後のリスト構造のテスト


これは考察ですが
判定がガバガバになるほど比較関数の文字数が多くなるのは、わざと入力を面倒にさせるためだと思っています。
もしくは厳密な比較のほうが多用するため、そちらの文字数を少なくしたのかもしれません。

zeropの存在意義

CommonLispには引数が0かどうか比較するzerop関数があります。

(zerop 1) ; -> NIL
(zerop 0) ; -> T
(zerop "0") ; -> Error! 引数がNumber型じゃない。
(zerop 0.0) ; -> T
(zerop -0) ; -> T
(zerop #c(0 0.0)) ; -> T

私にはこの関数の存在意義が分からないです。
普通に=で比較すればよいと思っています。
(文字数もzeropより少ないし)

(= 0 1) ; -> NIL
(= 0 0) ; -> T
(= 0 "0") ; -> Error! 引数がNumber型じゃない。
(= 0 0.0) ; -> T
(= 0 -0) ; -> T
(= 0 #c(0 0.0)) ; -> T

他の言語では0と比較する関数はないと思うのですが…
CommonLispの謎です。

and, or

CommonLispのand, orは他の言語と比べて分かりやすいです。(視認性がよい)

JS
if (isHoge && isFuga && isPiyo
    || isMoga && isMoge
    || isNago) {
  ...
}
CL
(when (or (and hogep fugap piyop)
          (and mogap mogep)
          nagop)
  ...)

JavaScriptのような記法だと

isHoge && isFuga && isPiyoが条件ね、はいはい
そんでorでisMoga && isMogeがあるのかー
さらにorでisNagoがあるのね。

という感じで全体の条件を理解するのにそれなりに頭を使ってしまいます。
CommonLispはorの箇所とandの箇所がブロック(四角形的な意味)(インデント)で分かれているのでパっと見で全体の条件が分かります。

反復処理

N回繰り返し
;; iは0から9まで代入される
(dotimes (i 10)
  (process1)
  (process2)
  ...)
リストの要素で繰り返し
(dolist (x '(1 2 3 4))
  (process1)
  (process2)
  ...)
loopマクロ

ものすごく多機能なループマクロ
とても私には解説できないので以下の記事を読みましょう。

少なくともwithとcollectは頭の片隅にいれておきましょう。

また意識低い系Lisperの私は以下のように使っています。

loopマクロを使ったコードを公開するときはマサカリが投げられることを覚悟したほうがいいです。
かなり多機能なので知識外のことがあるかもしれません。

余談

contiune

ないです。
ない!

GOTOみたいな構文もあるけど、使いたいと思わないので各自ググってください。

break

returnマクロでループを抜けることができます。
また、引数を指定するとそれをループの戻り値にできます。(戻り値のデフォルトはNIL)

(defun hoge ()
  (let ((sum 0))
    (dotimes (x 100)
      (incf sum x)
      (format t "~a " sum)
      (when (>= sum 30)
        (return x)))))

(format t "~a " (hoge)) ; -> 0 1 3 6 10 15 21 28 36 8
doマクロ

反復処理としてdoマクロというものもあります。
これはloopマクロよりもC言語のfor文に近いマクロとなっています。

なお、私は構文をいちいち覚えるのが面倒なので反復処理はdotimes, dolist, loopを使っていますが、
これを読んでいる人はdoマクロも使ってあげてください。

iterate

iterateという反復処理をするための外部ライブラリが存在します。
私は使ったことがないので有用性がよく分からないです。

多分、配列や連想配列をdolistのように処理出来て便利だとかそんな感じだと思います。(適当)

format関数

format関数でできることをざっとメモ書きしておきます。

  • ストリームに文字列を出力できる。
  • 複数の変数から文字列を生成できる。(C言語でいうところのsprintf関数)

format関数内で使える命令みたいなやつは沢山の機能がありますが、そこは以下のサイトを参考にしてください。

とりあえず、~%, ~aだけ覚えておけばいいです。
数値系はそのときにググりましょう。

また、第一引数をnilにするとストリームに文字列を出力せずに、戻り値として加工した文字列を取得できます。C言語でいうsprintf関数みたいに使えて便利です。


また可読性は最悪ですが、分岐や反復など、ある程度の制御処理も行えます。
ただ読みにくいので私は使っていません。

あーでも、~{~}はリストを展開できるので便利かもしれません。

(format t "~{~a! ~}" '(1 2 3)) ; -> 1! 2! 3!
芸術的なサンプル
lambda.lisp
(format t"~7@a~a~%~5@a~7@a~%~4@a~@*~9@a~%~a~10@a~%~a~11@a~%~@*~14@a~%~15@a~%~15@a~%~15@a*~%~14@a**~%~@*~13@a~4@a~%~12@a~5@a~%~11@a~@*~7@a~%~10@a~8@a~%~9@a~10@a~%~@*~8@a~11@a~11@a~%~7@a~13@a~@*~10@a~%~6@a~14@a~9@a~%~5@a~@*~16@a~7@a~%~a*~19@a~a""***""***""***""***""***")
出力
    ******
  ***    ***
 ***      ***
***       ***
***        ***
           ***
            ***
            ***
            ****
           *****
          *** ***
         ***  ***
        ***    ***
       ***     ***
      ***       ***
     ***        ***        ***
    ***          ***       ***
   ***           ***      ***
  ***             ***    ***
****                ******

引用元:https://ideone.com/XzPQoP (自作コード)

fizzbuzz.lisp
(defun fizzbuzz (n)
  (loop for n from 1 to n
     do (format t "~&~[~[FizzBuzz~:;Fizz~]~*~:;~[Buzz~*~:;~D~]~]~%"
                (mod n 3) (mod n 5) n)))
(fizzbuzz 30)
出力
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
16
17
Fizz
19
Buzz
Fizz
22
23
Fizz
Buzz
26
Fizz
28
29
FizzBuzz

引用元:https://eval.dan.co.jp/friends/sample/fizzbuzz.lsp

コメント

; セミコロンでコメントアウト

#|
  複数行はこれ
|#

余談

;;;; ファイルの先頭に書くようなプログラムの概要的なコメントはセミコロン4つ

;;; 関数の説明はセミコロン3つ

;; 通常のコメントはセミコロン2つ

; 処理の後ろにワンライナーでコメントを書くならセミコロン1つ

といった旨が逆引きCommonLispに書いてありました。(今はプライバシーエラーで見れない)
多分どこかに元ネタがあります。

関数

とほほ氏のサイトが簡潔で分かりやすいです。

こちらもかなり参考になります。

オプション引数(&optional)
キーワード引数(&key)
残り引数(&rest)
は便利なので覚えておきましょう。

基本

まあ、こんな感じです。

(defun tasizan (num1 num2)
  (+ num1 num2))

(tasizan 7 8) ; -> 15
クロージャ

letと組み合わせるとクロージャができます。

(let ((count 0))
  (defun countup ()
    (incf count)
    count))

(countup) ; -> 1
(countup) ; -> 2
(countup) ; -> 3
再帰関数

もちろん再帰処理も書けます。

(defun fib (index)
  (if (< index 2)
      1
      (+ (fib (1- index)) (fib (- index 2)))))

(fib 0) ; -> 1
(fib 1) ; -> 1
(fib 2) ; -> 2
(fib 3) ; -> 3
(fib 4) ; -> 5
optional, key, rest
;; 引数を省略するとnil、デフォルト値も設定可能
(defun hoge (&optional a (b 42))
  (format t "~a ~a~%" a b))

(hoge) ; -> NIL 42
(hoge 123 456) ; -> 123 456

;; キーで値指定、デフォルト値も設定可能
(defun fuga (&key a (b 42))
  (format t "~a ~a~%" a b))

(fuga) ; -> NIL 42
(fuga :a 123 :b 456) ; -> 123 456

;; 余った引数はリストで参照可能
(defun piyo (a &rest b)
  (format t "~a ~a~%" a b))

(piyo 123) ; -> 123 NIL
(piyo 123 456) ; -> 123 (456)
(piyo 123 456 789 'TIKUWA) ; -> 123 (456 789 TIKUWA)
traceマクロ

関数を実行する前にtraceマクロで指定すると、関数内で呼び出された関数が標準出力に出力されます。
デバグに利用しましょう。(そんな使わないけど)

(trace fib)
(fib 3)
結果
0: (FIB 3)
    1: (FIB 2)
      2: (FIB 1)
      2: FIB returned 1
      2: (FIB 0)
      2: FIB returned 1
    1: FIB returned 2
    1: (FIB 1)
    1: FIB returned 1
  0: FIB returned 3
3
関数を変数のように使う

イメージしにくいので最初にコードを提示しておきます。

(dolist (f '(+ - * /))
  (format t "1 ~a 2 = ~a~%" f (funcall f 1 2)))
出力
1 + 2 = 3
1 - 2 = -1
1 * 2 = 2
1 / 2 = 1/2

やはりとほほ氏のサイトが参考になります。

こちらのサイトも併せて読みたいです。

mapcar, funcall, applyくらいは覚えておきましょう。
some, everyもまあまあ出番があります。
でも使う機会が多いのはmapcarくらいです。

また、mapcarなどに関数のシンボルを引数で渡すときは#'(シャープシングルクォート)をつける必要があります。
厳密には'(シングルクォート)でいいものもあるらしい(※1)ですが、面倒くさいので何も考えずに#'(シャープシングルクォート)を付けときましょう。

※1
お気楽 Common Lisp プログラミング入門 高階関数とラムダ式
「関数はシンボルで渡すこともできる」を参照してください。
(雑に説明すると#'(シャープシングルクォート)ではなく'(シングルクォート)を利用すると局所的に定義した関数(※2)ではなく、グローバルに定義されている関数が実行されてしまうということらしい。)

※2
局所的に定義した関数というのは

の「ローカル関数」で解説されているlabelsマクロで定義された関数のことだと思われます。

なお、私はdefunで定義すればよいと思っているのでローカル関数を使ったことはありません。
(関数をプライベートにしたいときに使う感じか?多分違うな。パッケージの概念があるから。この辺の話は後々)

伝える気ゼロの個人的独り言(備忘録として)

call-next-methodっていうローカル関数があるんだが、なるほど、そういうことね。
ローカル関数はマクロと組み合わせるとかなり有効に使えるな。

後、分かり切っていると思いますが、無名関数はlambdaマクロで定義できます。

defunをlambdaに変えて、関数を格納するシンボルを無くしただけの構文だと覚えればいいです。

(mapcar (lambda (x) (* x 2)) '(1 2 3)) ; -> (2 4 6)

(functionp (lambda (x) (* x 2))) ; -> T
(type-of (lambda (x) (* x 2))) ; -> COMPILED-FUNCTION

;; 以下、おまけ

(defun hoge ())

(functionp 'hoge) ; -> NIL
(functionp #'hoge) ; -> T

(type-of 'hoge) ; -> SYMBOL
(type-of #'hoge) ; -> COMPILED-FUNCTION

;; よく分からんけど、hogeは箱で、#'は格納されている関数を取り出す感じかな。
;; てか、COMPILED-FUNCTIONって型があるのか。
;; CommonLispは関数単位でコンパイルできるのは知ってたけど。

以下のサイトの「ラムダ式は無名の関数を定義する」も参考にしてください。

余談

途中return

CommonLispの関数は基本的にdefun内の最後のS式の評価値が戻り値になりますが、途中で関数を抜けたい場面も多々あります。
そういうときは、return-from特殊オペレーターを使います。
第1引数に関数のシンボル、第2引数には戻り値(デフォルトNIL)を指定します。

(defun hoge (x)
  (when (< x 0) (return-from hoge "引数は0より大きい整数にしてね。"))
  (loop for i to x sum i))

(hoge -10) ; -> "引数は0より大きい整数にしてね。"
(hoge 10)  ; -> 55
defunは「でぃふん」か「でぃふぁん」か?

ChatGPTに尋ねてみたところ、defunは「でぃふぁん」と読むそうです。
ちなみに私は「でふん」派です。

#'の正体

#'は特殊オペレーターfunctionを実行するリーダーマクロです。
#がついているやつは漏れなくリーダーマクロの一種です。
見かけたら、あーこれはリーダーマクロだな!と思いましょう。(後で解説します。)
ところで「Special Operator」ってどう訳すのでしょうか。
特別式?特殊オペレーター?

CLHS: 2.4.8.2 Sharpsign Single-Quote
CLHS: Special Operator FUNCTION
CLHS: 2.4.8 Sharpsign

ちなみに'(シングルクォート)は特殊オペレーターquoteを実行するリーダーマクロです。

CLHS: 2.4.3 Single-Quote
CLHS: Special Operator QUOTE

整数同士の割り算

プログラミングしていると割り算の結果を小数点以下切り捨てにして取得したい場面があります。
そういう場合は以下のように書くと思います。

;; 割ってから切り捨て
(floor (/ 10 3)) ; -> 3

実はこの書き方は冗長です。
floorは割り算の機能が搭載されているので以下のように書くことができます。

(floor 10 3) ; -> 3

私よりも何百倍もLispに詳しくLispを愛している人物のコードを読んでいる最中に気付いた知識です。
それまでずっと前者の方法を使っていました。

なお、前者と後者では第2戻り値が異なりますが本題ではないので触れません。

多値の戻り値

CommonLispには多値の戻り値を返す関数が存在します。
それらを取得したい場合はmultiple-value-bindマクロを使いましょう。
第1戻り値だけほしい場合は特に何もする必要はありません。

;; 多値の取得
(multiple-value-bind (syou amari)
    (floor 10 3)
  (format t "商:~a 余り:~a~%" syou amari) ; -> 商:3 余り:1
  )

;; 第1戻り値だけほしいなら今まで通り
(let ((syou (floor 10 3)))
  (format t "商:~a~%" syou) ; -> 商:3
  )

;; ちょっと実験
;; format関数の~aは多値をどう表示する?
(format t "~a~%" (floor 10 3)) ; -> 3
;; 第1戻り値だけ表示してくれるね。かしこい。

なお、multiple-value-bindマクロの第2引数の多値を返すS式はインデントをひとつ増やしてください。
どこに書かれているのか知りませんが、そういうインデントルールがあります。
値をバインドした後、処理を書く系のマクロは大抵このルールが適応されます。覚えておきましょう。

関数でシングルクォート付きリストを戻り値にしてはいけない

予期せぬバグに繋がります。

On Lispにもっと詳細に書かれていましたが、どこに書かれているのかは忘れました。

オプション引数(&optional)とキーワード引数(&key)を同時に使ってはいけない

キーワード自体がシンボルの一種のためオプション引数だと誤認されます。

(defun hoge (&optional o &key k)
  (format t "o: ~a, k: ~a~%" o k))

(hoge 1)      ; -> o: 1, k: NIL
(hoge 1 :k 2) ; -> o: 1, k: 2
(hoge)        ; -> o: NIL, k: NIL

(hoge :k 2)   ; -> Error!
              ;    oに:kが代入された後、2ってなんだよ。kに代入するには:kって指定しろよ!
              ;    と解釈される。

もっとも、defunが実行されたときにSBCLから以下のように怒られるため気づけるとは思います。

; in: DEFUN HOGE
;     (SB-INT:NAMED-LAMBDA HOGE
;         (&OPTIONAL O &KEY K)
;       (BLOCK HOGE (FORMAT T "o: ~a, k: ~a~%" O K)))
;
; caught STYLE-WARNING:
;   &OPTIONAL and &KEY found in the same lambda list: (&OPTIONAL O &KEY K)
;
; compilation unit finished
;   caught 1 STYLE-WARNING condition

以下、余談
CommonLispには&optionalと&keyを両方使っているread-from-stringという初見殺し関数が存在します。

この関数は私がクワインを作ったときに使ったので思い入れがあります。(懐古)

read-from-stringとevalを組み合わせると、文字列をS式に変換して、そのS式を実行するというコンボが実現します。
ということは動的に任意の名前のシンボルを作れるのでは?!と寝る前にアイディアが思い浮かびました。
実際どうなるのかは自分で試してください。(適当)
(独り言:構造体作ったときに、構造体の名前を使った関数とか作られるけど、この仕組みを使ってんのかな?まあ調べれば分かるけど、メモ程度に書いとく。)

ドキュメント文字列

defunのメインの処理の前に文字列を書くと、それがドキュメント文字列となります。

(defun hoge ()
  "これは文字列「ほげほげ」を返す関数だよ!"
  "ほげほげ")

これはdescribe関数を使ったときに表示されます。(少なくともSBCLでは)

(describe 'hoge)
出力
COMMON-LISP-USER::HOGE
  [symbol]

HOGE names a compiled function:
  Lambda-list: ()
  Derived type: (FUNCTION NIL
                 (VALUES (SIMPLE-ARRAY CHARACTER (4)) &OPTIONAL))
  Documentation:
    これは文字列「ほげほげ」を返す関数だよ!
  Source form:
    (LAMBDA () "これは文字列「ほげほげ」を返す関数だよ!" (BLOCK HOGE "ほげほげ"))

また、スペシャル変数やマクロなどにもドキュメント文字列をつけることができます。

(defparameter *hoge* 123 "これは整数を格納する*hoge*変数だよ")
(describe '*hoge*)
出力
COMMON-LISP-USER::*HOGE*
  [symbol]

*HOGE* names a special variable:
  Value: 123
  Documentation:
    これは整数を格納する*hoge*変数だよ
再帰呼び出しとcar, cdr

Lispという言語は再帰呼び出しとなかなか相性がよいです。
その理由のひとつとしてcar, cdr(かー, くだー)の存在があると思います。

;; carはリストの先頭を取得する。
(car '(1 2 3)) ; -> 1
(car '())      ; -> NIL

;; cdrはリストの先頭を消したリストを取得する。
(cdr '(1 2 3)) ; -> (2 3)
(cdr '())      ; -> NIL

例としてリスト内の数値をすべて足し合わせるsum関数を作ると以下のようになります。

(defun sum (nums)
  (if nums
      (+ (car nums) (sum (cdr nums)))
      0))

(sum '(1 2 3 4 5)) ; -> 15

もっとも、sum関数を作るとしたら以下のように書くのが普通でしょう。

(defun sum (nums)
  (reduce #'+ nums))

(defun sum (nums)
  (apply #'+ nums))

QiitaのCommonLispのシンタックスハイライト、defunくらいは色変えてほしいなあ。

ラムダの話

lambdaをλで書けるようにするライブラリがあります。

ラムダといえばこれも欠かせませんね。

Schemeのロゴマークもλです。
Clojureのロゴマークもオシャレで好きです。

ラムダの由来もなかなか興味深いです。
以下のサイトの名前の由来で語られています。

λを左右反転した記号ʎは「硬口蓋側面接近音」といいます。

また「入学式」と「λ式」は似ていると思います。

値(その2)

リスト
;; 定義
(defparameter *list* '(123 456 789))

;; 取得
(elt *list* 1) ; -> 456
(nth 2 *list*) ; -> 789

;; 代入
(setf (elt *list* 1) 42)
(setf (nth 2 *list*) 1337)

;; 確認
*list* ; -> (123 42 1337)

ちなみにeltはelementの略だと思われます。

配列
定義
(make-array 3) ; -> #(0 0 0)
(make-array 3 :initial-element 42) ; -> #(42 42 42)
(make-array 3 :initial-contents '(123 456 789)) ; -> #(123 456 789)

(make-array '(2 3) :initial-contents '((1 2 3) (4 5 6))) ; -> #2A((1 2 3) (4 5 6))

;; なお、値ありの一次元配列ならvectorで定義したほうが楽
(vector 123 456 789) ; -> #(123 456 789)
基本操作
;; 定義
(defparameter *array1* (make-array 3 :initial-contents '(123 456 789)))

;; 取得
(aref *array1* 2) ; -> 789
;; 代入
(setf (aref *array1* 2) 1337)
;; 確認
*array1* ; -> #(123 456 1337)

;; 定義
(defparameter *array2* (make-array '(2 3) :initial-contents '((1 2 3) (4 5 6))))

;; 取得
(aref *array2* 1 2) ; -> 6
;; 代入
(setf (aref *array2* 1 2) 1337)
;; 確認
*array2* ; -> #2A((1 2 3) (4 5 1337))

個人的感想ですが、あまり配列を使うことがないと思います。
CommonLispがかなりリスト操作に特化しているので基本的にリストのほうが楽です。

参考サイト
CLHS:Function MAKE-ARRAY
お気楽 Common Lisp プログラミング入門:配列

属性リスト(plist)

多分CommonLisp特有のデータ構造です。
個人的に次の項目で説明する連想リストや構造体のほうが好きなので、これを意図的に使おうと思いません。
あるとしたらライブラリやフレームワークに使用を強要されるときくらいでしょう。
というか使ったことないです。
getfという超シンプルなマクロ名を見るに、CommonLispの開発者的にはplist押しだったのかもしれません。

;; 定義
(defparameter *plist* '(:name "鈴木" :height 170 :weight 74))

;; 取得
(getf *plist* :name) ; -> "鈴木"
;; 代入
(setf (getf *plist* :name) "田所")
;; 確認
*plist* ; -> (:NAME "田所" :HEIGHT 170 :WEIGHT 74)

構文はJavaScriptのオブジェクトに似てます。

参考サイト
CLHS:Accessor SYMBOL-PLIST
お気楽 Common Lisp プログラミング入門:属性リスト
CLtL2:10.1. The Property List
common-lisp Tutorial => Property Lists

連想リスト(alist)
;; 定義
(defparameter *alist* '((:name . "鈴木") (:height . 170) (:weight . 74)))

;; 取得
(assoc :name *alist*) ; -> (:NAME . "鈴木")
(cdr (assoc :name *alist*)) ; -> "鈴木"
;; 代入
(setf (cdr (assoc :name *alist*)) "田所")
;; 確認
*alist* ; -> ((:NAME . "田所") (:HEIGHT . 170) (:WEIGHT . 74))

.(ピリオド)はコンスセルを作るやつです。中間記法のCommonLispの異端児です。
また、assoc-utilsという連想リストをより楽に操作するためのライブラリがあります。

余談ですが、CommonLispの調べものをしていると高頻度で深町氏の名前を目にします。
CommonLispのWebフレームワークとかを作っているすごい人物です。

参考サイト
CLtL2:15.6. Association Lists

構造体
;; 構造体の定義
(defstruct human
  (name "")
  (height 0)
  (weight 0))

;; 生成
(defparameter *human*
              (make-human :name "鈴木"
                          :height 170
                          :weight 74))

;; 確認
*human* ; -> #S(HUMAN :NAME "鈴木" :HEIGHT 170 :WEIGHT 74)

;; 各要素の取得と代入
(with-slots (name height weight) *human*
  (format t "~a: 身長は~acmで、体重は~akgです。~%" name height weight)
  ; └-> 鈴木: 身長は170cmで、体重は74kgです。
  (setf name "田所"))

;; 確認
*human* ; -> #S(HUMAN :NAME "田所" :HEIGHT 170 :WEIGHT 74)

;; with-slotsはスロットに対して任意の変数名で紐づけ可能
(with-slots ((n name)
             (h height)
             (w weight)) *human*
  (format t "~a: 身長は~acmで、体重は~akgです。~%" n h w)
  ; └-> 田所: 身長は170cmで、体重は74kgです。
  )

with-slotsマクロは構造体の複数のスロットを利用するときに便利です。
ただ、単一のスロットのみを利用したいときは以下の方法も使えます。

;; 生成
(defparameter *human*
              (make-human :name "鈴木"
                          :height 170
                          :weight 74))

;; 取得
(human-name *human*) ; -> "鈴木"
;; 代入
(setf (human-name *human*) "田所")
;; 確認
*human* ; -> #S(HUMAN :NAME "田所" :HEIGHT 170 :WEIGHT 74)

説明が遅くなりましたが、構造体は定義と同時に、defstructマクロ内で定義した構造体名とスロット名で
make-[構造体名]、[構造体名]-[スロット名]の関数が生成されます。

また、継承も行えます。
説明は省略しますが、まあ、一応雑に書いときます。

(defstruct hoge
  (a 0))

(defstruct (fuga (:include hoge))
  (b 0))

参考サイト
お気楽 Common Lisp プログラミング入門:構造体
CLHS:Macro DEFSTRUCT

クラス

各自でググってください。
ほぼ構造体と同じです。
違いは複数クラスから継承できることくらいです。(多分)

まあ、雑に書いときます。

(defclass animal ()
  ((height :initform 0 :initarg :height)
   (weight :initform 0 :initarg :weight)))

(defclass human (animal)
  ((hoge :initform "staticは:allocationで:classを指定するとできる。" :allocation :class)
   (name :initform "" :initarg :name)))

;; いちいちinitargを設定しないといけないのめんどくさいなあ。

(defparameter *human* (make-instance 'human :name "鈴木" :height 170 :weight 74))

(with-slots (name height weight) *human*
  (format t "~a: 身長は~acmで、体重は~akgです。~%" name height weight)
  ; └-> 鈴木: 身長は170cmで、体重は74kgです。
  )

;; 型とかアクセッサーとかは自分でググってください。

参考サイト
お気楽 CLOS プログラミング入門:オブジェクト指向の基礎知識
CLHS:Macro DEFCLASS

駄文

個人的にCommonLispのクラスは好きではないです。
フレームワークやライブラリに使用を強要されない限り自分で定義しようとは思いません。
定義の方法もなんか複雑だし、継承もメソッドの利用も構造体でもできるし、
特徴的な複数クラス継承可能という特性も必要になることがまずないからです。

こういうこと書くとLispエイリアンが怒りそうですが
ゴリゴリにオブジェクト指向がしたいなら、わざわざCommonLispを使わないで、
それに特化している言語を使うべきでは?と思ってしまいます。
CommonLisp君に対して、「競うな、持ち味を生かせ!」って思っちゃいます。
あー、叩かれちゃうなー!!怖いなー!!
(CLOSはオブジェクト指向に乗り遅れないために強引に無理やりクラス対応させました。って感じがする。知らんけど。でもCLOSで誕生したであろうsetfとかdefmethodとかはすごく便利です。)

ポール・グレアム氏もCLOSがお好きではない模様。
なぜArcはとりたててオブジェクト指向でないのか---Why Arc Isn't Especially Object-Oriented---

余談

構造体 VS クラス

どっち使えばいいんだYO!!って感じでいろいろ調べたときに見たサイトです。

Structure vs Class, case in Common Lisp.
Difference between struct and class in Common Lisp
Do you start with a struct or a class?

なお、私は構造体押しです。

構造体とクラスの命名規約?

個人的意見ですが、タイトルの通り、構造体とクラスの命名規約が分かりません。
(簡単のためクラスを想定して話を続けます。)
いやいやそんなのないでしょ。ってのは分かりますし、実際そうなんでしょうが、
CommonLisp特有の事情でちょっぴり嫌なことがあるのです。
それは… クラス名とインスタンス名が同じだと区別がつかない!ということです。

例えば、Javaなどではクラス名の先頭を大文字、インスタンス名は先頭を小文字にすれば区別がつきます。
しかし、CommonLispは基本的にシンボルの大文字と小文字を区別しないので(というより、大文字を使ってはいけないような圧力を感じる)区別がつきません。

いや、別に区別がつかなかったところで特に害はないのですが、他のオブジェクト指向の言語に慣れている身からしてみたら、なんか気持ち悪く感じてしまいます。

構造体の比較

構造体の同値性を比較するときに楽をしたいならequalp関数が便利です。

(defstruct human
  (name "")
  (height 0)
  (weight 0))

(defparameter *human1* (make-human :name "鈴木" :height 170 :weight 74))
(defparameter *human2* (make-human :name "鈴木" :height 170 :weight 74))

(eq *human1* *human2*) ; -> NIL
(eql *human1* *human2*) ; -> NIL
(equal *human1* *human2*) ; -> NIL
(equalp *human1* *human2*) ; -> T

;; ただし、equalpは判定がゆるいということを理解した上で使うこと。
(defparameter *human3* (make-human :name "鈴木" :height 170.0 :weight #c(74 0)))
(equalp *human1* *human3*) ; -> T

なお、クラスで同様のこと(equalpでのガバ同値比較)は行えません。
スロットが同じ値でもNILになります。

Enumってあるの?

ないです。
ない!

defconstantや後述するキーワードで代用すればいいんじゃないでしょうか。(適当)

メソッド

引数の型が違えば同じ名前のメソッドを作れ、かつ、引数の型に応じて対応するメソッドを呼び出してくれる、オーバーロードとかオーバーライドとかで有名なオブジェクト指向の言語で親の顔より定義したメソッドと同じようなものです。

ほぼ関数と同じ構文です。

(defstruct hoge)

;; メソッド定義

(defmethod show ((x hoge))
  (format t "hoge~%"))

(defmethod show ((x integer))
  (format t "integer~%"))

(defmethod show ((x string))
  (format t "string~%"))

;; 実行
;; メソッド名が同じでも引数によって呼び出されるメソッドが異なる。

(show (make-hoge)) ; -> hoge
(show 123)    ; -> integer
(show "なごなご") ; -> string

継承元のメソッドを呼び出す場合は、call-next-method関数を使います。

(defstruct hoge)
(defstruct (fuga (:include hoge)))

;; メソッド定義

(defmethod show ((x hoge))
  (format t "hoge~%"))

(defmethod show ((x fuga))
  (call-next-method) ; 親のメソッドの呼び出し
  (format t "fuga~%"))

;; 実行

(show (make-fuga)) ; -> hoge
                   ;    fuga

参考リンク
お気楽 CLOS プログラミング入門:オブジェクト指向の基礎知識
「メソッドの定義」の箇所を参考にしてください。
CLHS:Macro DEFMETHOD

マクロ

Lisper大好きマクロです。
でも実際使う場面はほとんどないです。
可読性が低く、デバグもしにくいのであまり好きではないです。
Googleもマクロでなく関数で実現できるなら関数を使え!と言っています(※)。
ですがLispの特徴的な機能なので知っておきましょう。

Google Common Lisp スタイルガイド 日本語訳:マクロ

マクロ定義(基本中の基本)

基本的にdefunをdefmacroに変えただけの構文だと覚えればいいです。

(defmacro hoge (a b)
  (+ a b))

(hoge 1 2) ; -> 3

関数と何が違うんだよ!って感じですが、マクロは実行前に戻り値が呼び出し元に展開されます。
(多分、インタプリタならマクロが呼び出されたときに、コンパイルならコンパイル時に展開されているんだと思います。)
上記のプログラムだとこうなっているはずです。

(defmacro hoge (a b)
  (+ a b))

(+ 1 2)

このイメージが頭の中にあれば、大抵のマクロは理解できるようになると思います。

なお、上記の足し算みたいな目的でマクロを定義することはないです。
単純に値を加工して返すことを目的とした操作がしたいなら関数を使ってください。
可読性やデバグのしやすさはマクロより関数のほうが何倍も優れています。

(上記の例はコンパイラが自動で3に置き換えそうだからちょっと挙動が違うかも。ま、(マクロの挙動を完全に理解しているわけではないから)多少はね?)

マクロ定義(よく見るやつ)

例として、自作for文を提示しておきます。

(defmacro for ((var start whilep update) &body body)
  `(loop with ,var = ,start
         while ,whilep
         do ,@body
            (setf ,var ,update)))
使用例
(for (i 0 (< i 10) (+ i 2))
     (format t "-----~%")
     (format t "~a = ~a~%" 'i i))
出力
-----
I = 0
-----
I = 2
-----
I = 4
-----
I = 6
-----
I = 8

まず最初に知っておきたいのは&bodyです。
&bodyはdefunでいう&restと同じだと思って構いません。
(&rest:可変長の引数をリストに詰めて利用できるようにするやつ)
(ただ、&bodyは&restと違って引数が評価されません。
これは&bodyの機能ではなくマクロの引数の特徴です。)
まあ、主となるS式を書く箇所なのでbodyという名なのでしょう。

(defmacro hoge (&body body)
  (format t "~a~%" body))

(hoge 123 "tikuwa" t 'namako (+ 1 2 3)) ; -> (123 tikuwa T 'NAMAKO (+ 1 2 3))

;; おまけ
;; マクロは引数を評価しないというお話

(defun f (a) (format t "~a~%" a))
(defmacro m (a) (format t "~a~%" a))

(f (+ 1 2 3)) ; -> 6
(m (+ 1 2 3)) ; -> (+ 1 2 3)

次に`(バッククォート)の機能を理解する必要があります。
`(バッククォート)は'(シングルクォート)と同様に何もするなという意味ですが、
'(シングルクォート)と違い対象のS式の中で特殊な記号(, と ,@)を利用することができます。

説明しにくいのですが…

,(コンマ)は値?をそのままはめ込め、
,@(コンマアット)は外側のカッコを消してからはめ込むことができます。

以下のコードも参考にしてください。

(defmacro hoge (num list)
  `(progn
      (format t "~a~%" ,num)
      (format t "~a ~a ~a~%" ,@list)))

(hoge 42 (1 2 3))

; ↓ 次のように展開される。

(progn
  (format t "~a~%" 42)
  (format t "~a ~a ~a~%" 1 2 3)))

,@listの箇所で外側のカッコが外れていることが分かると思います。
(JavaScriptのスプレッド構文に似てるなー)

というわけで、最初に提示した自作for文は以下のように展開されます。

(defmacro for ((var start whilep update) &body body)
  `(loop with ,var = ,start
         while ,whilep
         do ,@body
            (setf ,var ,update)))

(for (i 0 (< i 10) (+ i 2))
  (format t "-----~%")
  (format t "~a = ~a~%" 'i i))

; ↓ 次のように展開される。

(loop with i = 0
      while (< i 10)
      do (format t "-----~%")
         (format t "~a = ~a~%" 'i i)
         (setf i (+ i 2))))

実際、マクロを定義すると構文エラーでバグりまくると思います。
そんな時にはmacroexpand-1関数が便利です。

macroexpand-1関数はマクロが展開された結果を返してくれる関数です。
戻り値は評価されていないS式なので構文エラーも発生しません。
じっくりとデバッグしましょう。

(defmacro for ((var start whilep update) &body body)
  `(loop with ,var = ,start
         while ,whilep
         do ,@body
            (setf ,var ,update)))

(format t "~a~%"
          (macroexpand-1 '(for (i 0 (< i 10) (+ i 2))
                            (format t "-----~%")
                            (format t "~a = ~a~%" 'i i))))
出力
(LOOP WITH I = 0
      WHILE (< I 10)
      DO (FORMAT T -----~%) (FORMAT T ~a = ~a~% 'I I) (SETF I (+ I 2)))

ちなみに個人的マクロ定義テクニックですが、
戻り値には`(バッククォート)をつける。
引数は思考停止で,(コンマ)をつける。
ただし&bodyの引数は,@(コンマアット)をつける。
としておけば大抵どうにかなります。

余談

macroexpand関数

macroexpandというmacroexpand-1の元ネタみたいな関数があります。
これはmacroexpand-1と違いマクロを再帰的に展開します。
要するにマクロの中のマクロも展開してしまいます。
macroexpand-1は一番外側のマクロしか展開しないので基本はこちらをデバグで使います。

`(バッククォート)の別な使い方

`(バッククォート)はマクロ定義以外でも利用することができます。
大抵、リストを加工したいときに使ったりします。

こんな感じです。(いい例が思いつかない!)

`(1 2 ,(+ 1 2)) ; -> (1 2 3)

;; シングルクォートだと評価されない。
'(1 2 (+ 1 2)) ; -> (1 2 (+ 1 2))

;; 自分はこっち派
(list 1 2 (+ 1 2)) ; -> (1 2 3)

分かりづらいのでlist関数使ってほしいなーって思っちゃいます。

結局マクロっていつ定義するのか?

個人の感想です。

まず大切なこととして…
マクロは可読性とデバグ性が悪いので関数で実現できる機能は関数を利用してくださいお願いします。
ということです。
何回でも言います。
関数で実現できることをわざわざマクロ定義しないでください。
(Lispエイリアン達が作るフレームワークやライブラリではご自由にマクロ定義してください。
にわかLisperの私が口出しする気はさらさらありません。
これは私から見た一般的なアプリ開発での話です。コンピュータサイエンス的な話ではなく、ビジネスロジック的な話です。)
マクロだと関数呼び出しのオーバーヘッドがないというのは誤差みたいなものです。(個人の偏見)
普通は気にもなりません。仮に遅いなら大抵の場合、あなたが実装しているアルゴリズムや環境の問題です。(もしくは関数に型を指定してコンパイルしてください。)

と、いうことは逆説的に関数では出来ないことをマクロ定義するべきです。

例えば…

定義
defun, defmacro, defpackageなど

条件分岐
when, unless, condなど

ループ
dotimes, dolist, loop, doなど

内部的にtry-catchみたいなことをしているやつ
with-open-file(エラーが発生してもストリームを閉じてくれる)など

みたいな感じです。
(それでもあんまり腑に落ちないなー)

特に定義関連はマクロが活躍します。
いちいちletなどで変数を定義する手間がなくなるためこれに関しては間違いなくマクロを使うべきです。
(あー、説明が難しいなー。言いたいことは下記のコードを読んで察してください。)

;; 例えば、dotimesマクロってありますよね?
;; そいつの引数でindexを格納する変数を指定しますよね?
;; でもですよ?変数っていうのは普通はletなどで定義しないと使えないんですよ?
;; いちいちdotimesマクロを呼び出す前にletで定義しないで使えるのは
;; dotimesマクロの中でletで定義しているからなんですよね。
;; 仮にdotimesマクロ内にletがない世界だと以下のように書かないといけないんですよ?

(defmacro darui-dotimes ((var until) &body body)
  `(loop while (< ,var ,until)
         do ,@body
            (incf ,var)))

(let ((i 0))
  (darui-dotimes (i 5)
    (format t "~a " i))) ; -> 0 1 2 3 4

;; これはだるい!
;; それに対してdotimesマクロのこのシンプルさ。美しいね。

(dotimes (i 5)
  (format t "~a " i)) ; -> 0 1 2 3 4

;; 関数だと引数を評価しちゃうから、
;; 定義関連はやはりマクロが最強だね。

まあ、On Lisp読みましょう。

後、関数で実現できるならマクロ定義しないでください。(N回目)
関数で実現できるならマクロ定義しないでください。(N+1回目)
保守するプログラマの生命にかかわります。

gensys

個人的独り言

マクロ内で変数を定義するときにgensysで生成したりします。
確か外側のシンボルとダブらないようにするため的な意味合いだったと思います。
正直、理解していません。
On Lispでも言及されているので気になった人は調べてください。

以下のようなプログラムを見ていると本当にマクロマクロしてるなあって感じます。

リーダーマクロ

マクロは続くよどこまでも。という感じでまたまたマクロです。
Lispはマクロばっかでやんなっちゃいます。

リーダーマクロはシンボルとか値の頭につけるマクロです。(多分)
有名どころは'(quote)とか#'(function)とか#\(character)とか#p(pathname)とかです。

ぶっちゃけ意図的に定義したことがないので鵜呑みしないようにしてください。

駄文

まあ結論から言うと、リーダーマクロを独自定義することはほぼないです。
アプリ開発ならなおさら独自定義することはないです。

CommonLispでよく定義する機能ランキングはこんな感じです。(N=1の個人の偏見です。)
関数&メソッド(98%)>>> マクロ(1.9999%)>>>>>>>>>>>>>>>>>> リーダーマクロ(0.0001%)

(リーダーマクロを呼び出す名前で使える文字数が少なくて他のパッケージと競合しそう。
その辺どうやってるんだろうか。まあ、使わないからどうでもいいけど。)
(てか、考えて名前をつけないと既存シンボルを破壊するんじゃないのか?これ。個人開発ならいいけど、チーム開発だったらその辺は考慮しないといけないかもなー。)

1文字のやつ
(set-macro-character #\%
  #'(lambda (stream char)
      (declare (ignore char))
      (format nil (read stream t nil t))))

%"文字列を~%簡単に~%改行できるようにする~%リーダーマクロ%を~%作ったよ!!"
出力
"文字列を
簡単に
改行できるようにする
リーダーマクロ%を
作ったよ!!"

見れば分かると思うのでいちいち説明しません。(丸投げ)
(declare (ignore char))
(read stream t nil t)
は決まり文句みたいなもんです。

参考リンク
CLHS:Function SET-MACRO-CHARACTER, GET-MACRO-CHARACTER

2文字のやつ
(set-dispatch-macro-character #\# #\%
  #'(lambda (stream char1 char2)
      (declare (ignore char1 char2))
      (format nil (read stream t nil t))))

#%"文字列を~%簡単に~%改行できるようにする~%リーダーマクロ#%を~%作ったよ!!"
出力
"文字列を
簡単に
改行できるようにする
リーダーマクロ#%を
作ったよ!!"

参考リンク
Function SET-DISPATCH-MACRO-CHARACTER, GET-DISPATCH-MACRO-CHARACTER

もっとリーダーマクロのことが知りたい人はOn Lispを読みましょう。

余談

リーダーマクロ誕生物語(想像)

雑にリーダーマクロ誕生物語を書きます。
完全に私の想像であり、実際どうなのかは知らないので悪しからず。

~ リーダーマクロが存在しなかった時代 ~

リストを定義するときとかシンボルを評価したくないときにいちいち(quote hogehoge)って
書かないといけないのクソめんどくさいなあー
ましてや、多用するような機能だからもっと楽に記述できるようにしたいなー

コンパイラの構文解析の処理を拡張してもいいけど、それもそれで面倒だし、
Lispは単純な構文や拡張性が強みだから、マクロとして定義できるようにしたいなー

こうして1文字限定のリーダーマクロが生まれた。
しかし、これは実質的にはquote特殊オペレーターを呼び出すためだけに作られたマクロであった。

そんな中、quote以外のリーダーマクロを定義する人々が現れた。
するとどうだろう。彼らが好き勝手に1文字のリーダーマクロを定義した結果、
既存プログラムのシンボル名の先頭の文字が偶然リーダーマクロ判定に引っかかりバグる事態が多発したのだ。

この事態を重く見たLisper達は2文字でリーダーマクロを定義できるようにし、かつ
リーダーマクロは最初に#(シャープ)を付けることを暗黙のルールとすることで解決を図ったのであった。

めでたしめでたし。

CommonLisp側で定義されているリーダーマクロ達
全て

王道を征くシャープがついているリーダーマクロ達

#p(pathname)の存在意義

文字列がパスであることを示す際に#pをつけているプログラムを多く目にすると思います。

こんなのとか
(load #p"main.lisp")

ただ、大抵の場合、いや、少なくとも私が触れてきたパスを記述する系のすべての関数、マクロで
別に#pをつけなくても、期待する動作をしてくれました。

こんな感じ
(load "main.lisp")

だとしたら、わざわざ#pと記述するのは冗長です。
では何故いちいち#pを書くのか?
個人的意見としては純粋に第三者が見たとき用にパスであることをアピールするため、
もしくは本当の意味や目的を知らないけど、周りに合わせているためだと思っています。

天下のSBCL様はload関数に関しては#pをつけてないですね。

もちろん、pathname構造体でなければいけない場面なら#pをつけましょう。
CommonLispエンジョイ勢の私にとって、それがどういう場面なのかは知りませんが。

参考リンク
CLHS:2.4.8.14 Sharpsign P
CLHS:Function PATHNAME
CLHS:Issue PATHNAME-PRINT-READ Writeup

(ぼくがかんがえたさいきょうの)インデントルール

特にインデントルールが明言されているわけではないと思いますが、
よく見かけるやつを書いておきます。
注意!以下はすべて私がそうだろうなーって思って書いているだけであり、特に根拠があるわけではないです。あなたやあなたの開発チームにとってメンテナンスしやすい形式ならば、それが正解です。

LEM使えば勝手に揃えてくれるというのは無しの方向でお願いします。

スペース派

インデントにタブを使ってはいけません。
スペース2個が基本です。

閉じカッコ

閉じカッコは一か所にまとめましょう。
C言語みたいに始まりと終わりの箇所を一緒の高さにしてはいけません。

(fugafuga (hoge
           hoge
           (hoge 
            hoge
            (hoge
             hoge
             (hoge hoge))))) ; 閉じカッコはまとめること

でもこれは許されます。

;; 閉じカッコの直前にコメントがある場合は改行してもいい。
(fugafuga hoge
          hoge
          hoge ; この行に関するコメント
          )

;; これはなんか嫌
(fugafuga hoge
          hoge
          hoge) ; この行に関するコメント

参考にSBLCのソースを貼っておきます。

改行後のインデント(関数系)

基本的に、改行したら第1引数とインデントを揃えてください。

;; 第1引数にインデントを揃える。
(fugafuga hoge
          hoge
          hoge)

;; 改行したら第1引数に揃える!
(fugafuga hoge hoge hoge
          hoge
          hoge)

;; これはダメ
(fugafuga hoge hoge
               hoge
               hoge)

あまりしませんが、関数の第1引数の前で改行する場合は、関数名、マクロ名と同じ高さに揃えてください。

;; 関数名、マクロ名に高さを揃える。(ただし、後述する&body的な引数があるマクロは別)
(fugafuga
 hoge
 hoge
 hoge)

参考にSBLCのソースを貼っておきます。

駄文

どうでもいいけど、assertってマクロだったのか… 関数だと思ってた。
ああ、確かに引数じゃなくてマクロ内で評価したいかもね。
例外が発生しても止まらずに動くようにしたいからね。知らんけど。

改行後のインデント(マクロ系)

&body的な引数があるマクロの場合、改行した際に第1引数に高さを合わせる必要はなく、
普通にインデントを1つ増やせばよいです。

有名どころだとdefunとかletとかwhenとかdolistとかです。

;; よく見るやつなんで、特に意識せずに正しくインデントできると思います。
(fugafuga (moga
           moga
           moga)
  hoge ; &body的な引数
  hoge
  hoge)

あまりしませんが、&body的な引数があるマクロで第1引数の前で改行する場合も普通にインデントを1つ増やせばよいです。

;; 関数と違ってインデントを増やしていい。
(fugafuga
  hoge
  hoge
  hoge
  hoge)

;; 逆にこれはダメ
(fugafuga
 hoge
 hoge
 hoge
 hoge)

参考にSBLCのソースを貼っておきます。

改行後のインデント(bind系)

multiple-value-bindやdestructuring-bindのような評価した値を変数に紐づける系のマクロの場合、
変数に紐づける値を返すS式のインデントを&body的な引数の箇所よりひとつインデントを増やす必要があります。

(fugafuga (x y z)
    (mogamoga) ; 紐づけたい結果を返すS式
  hoge ; &body的な引数
  hoge
  hoge)

参考にSBLCのソースを貼っておきます。

その他

'(シングルクォート)や#'(シャープシングルクォート)でちょこっと通常のインデントとずれた場合、
そのちょこっとずれた分も補完してください。

(fugafuga #'(lambda (x y)
              (+ x y))
          '(hoge1
            hoge2
            hoge3))

;; これはダメってこと
(fugafuga #'(lambda (x y)
            (+ x y))
          '(hoge1
           hoge2
           hoge3))

参考にSBLCのソースを貼っておきます。

その他、unwind-protectみたいにbind系みたいなインデントルールがあるマクロが存在します。
多分、&body的な引数以外の箇所はすべて&body的な引数よりインデントをひとつ増やすというルールがあるような気がします。

まあ、迷ったらSBCLを参考にしましょう。
俺が間違っているならSBCLも間違っている!という主張ができるので。

参考になるリンク集

余談

コッカ

昔のLisperは閉じカッコのことをコッカと読んでいたらしいです。

閉じカッコ!?

暇つぶしに読んでいたOSSのコードで見つけた閉じカッコの前が改行されているケース。

これは多分、リストに要素を簡単に追加できるようにするためのTipsだと思われます。
JavaScriptでいうところの末尾のカンマみたいな感じです。

ずれてる!?

暇つぶしに読んでいたOSSのコードで見つけたインデントが揃っていないケース。

コロンで揃えていますが、これはこれで見栄えがよいです。

閉じカッコいる?

CommonLispでプログラミングしていると、別にこれ構文として閉じカッコなくてもよくね?
と思うときがあります。何なら、開始のカッコもなくてもいいよね。とすら思います。
ってPythonっぽいですね。
オチはないです。

ちなみにPythonといえばHyが有名です。

このような記事もありました。

キーワード

別に重要ってわけでもないし、知らなくてもコードは書けるので読み飛ばしてもいいです。

キーワードというのは先頭に:(コロン)がついているやつです。
キーワードはKEYWORDパッケージのシンボルであり、:(コロン)を頭につけてシンボル名を付けると勝手に定義されます。(パッケージについては後々解説します。)
だから何だよ!って感じですが、これは主に文字通りキーワードを目的にしたい場面
例えば、関数の&keyや、plistのkeyとか、クラスのinitargとかで使われたり使ったりします。

:hogehoge ; :HOGEHOGE

(keywordp :hogehoge) ; -> T

(describe :hogehoge)
; ↓
; :HOGEHOGE
;   [symbol]
;
; HOGEHOGE names a constant variable:
;   Value: :HOGEHOGE

(package-name (symbol-package :hogehoge)) ; -> "KEYWORD"

(eq :hogehoge :hogehoge) ; -> T

(string :hogehoge) ; -> "HOGEHOGE"

Enum代わりに使うのも面白いと思います。

駄文

個人的に、これが存在するおかげでデータを紐づけるkeyを文字列で定義せずに済んでいると思っています。
:hogehoge と "hogehoge"
どっちがいいと言われたら間違いなく前者でしょう。
:(コロン)は右手の小指をちょっと動かすだけでいいけど
"(ダブルクォート)は入力が面倒だし、2回押さないといけないからね。
後、CommonLispってなんか知らんけど内部的には大文字が使われているから、
多分その辺の整合性というか簡便化に一役買っていると思われます。
(補足:キーワードは文字列型に変換すると大文字のシンボル名になる。)

説明しないけど知っておいたほうがいいんでない的なやつ

雑に箇所書き

  • 文字列操作
  • 日付、時間
  • 乱数
  • 例外処理
  • ファイル操作
  • 関数の型付け
  • 最適化とかのコンパイル関連の話
  • RoswellScript
  • Alexandria, UIOP

パッケージ

長かった備忘録ももう終盤か、といった感じです。
やる気ゲージが空ですが、ダラダラと書き殴ります。

パッケージはJavaScriptでいうところのモジュール
C#でいうところの名前空間みたいなもんです。

グダグダ話すのも面倒だし、記事を書く気力も無くなってきたので、いきなり結論を書きます。
こうやって書くのだ!(非推奨の方法)

fizzbuzz.lisp
(provide :fizzbuzz)
(defpackage :fizzbuzz
  (:nicknames :fb)
  (:use :cl)
  (:export :fizzbuzz
           :create-fizzbuzz-list))
(in-package :fizzbuzz)

(defconstant +fizz+ 3)
(defconstant +buzz+ 5)

(defun fizzp (num)
  (zerop (mod num +fizz+)))

(defun buzzp (num)
  (zerop (mod num +buzz+)))

(defun fizzbuzz (num)
  (let ((fizzp (fizzp num))
        (buzzp (buzzp num)))
    (cond
      ((and fizzp buzzp) "FIZZBUZZ")
      (fizzp "FIZZ")
      (buzzp "BUZZ")
      (t num))))

(defun create-fizzbuzz-list (start end)
  (loop for i from start to end
        collect (fizzbuzz i)))
main.lisp
(require :fizzbuzz "fizzbuzz.lisp")

(defun main ()
  (format t "~{~a~%~}" (fizzbuzz:create-fizzbuzz-list 1 15)))
実行
ros main.lisp
出力
1
2
FIZZ
4
BUZZ
FIZZ
7
8
FIZZ
BUZZ
11
FIZZ
13
14
FIZZBUZZ
パッケージの取り込み(require)

順を追って説明します。

まずパッケージを利用するにはいろいろと方法がありますが、
とりあえずお手軽なのはrequireでしょう。
(CLHS曰く非推奨だとさ。悲しいね。)

fizzbuzzパッケージを取り込むには以下のように書きます。

(require :fizzbuzz "fizzbuzz.lisp")

;; ニックネームでも可能
;; またニックネーム以外の任意の名前を付けることはできない。
(require :fb "fizzbuzz.lisp")

;; こう書くこともできる。
;; 第1引数は内部でstringに変換していると思われる。
(require "fizzbuzz" "fizzbuzz.lisp")
(require #:fizzbuzz "fizzbuzz.lisp")

取り込んだ後は、「パッケージ名:exportされているやつ」と書くことで利用できます。

(fizzbuzz:fizzbuzz 3) ; -> "FIZZ"

;; パッケージ名はニックネームでもよい。
(fz:fizzbuzz 5) ; -> "BUZZ"

また、「パッケージ名::exportされていないやつ」とすることで公開していないシンボルも参照できます。
(あまり使わないです。)

fizzbuzz::+fizz+ ; -> 3

パッケージ名を省略したい場合はuse-package関数を使います。

(use-package :fizzbuzz)

(fizzbuzz 15) ; -> "FIZZBUZZ"
パッケージ定義

パッケージを定義するにはdefpackageマクロを使います。

(defpackage :fizzbuzz
  (:nicknames :fb)
  (:use :cl)
  (:export :fizzbuzz
           :create-fizzbuzz-list))

:nicknamesはニックネーム(なくてもいい)を
:useはuse-packageしたいパッケージ名を
:exportは外部公開したいシンボル名を指定します。

:useで:clと書かれていますが、これはCOMMON-LISP-USERパッケージを取り込んでいます。
COMMON-LISP-USERパッケージはデフォルトのパッケージであり、無意識に使っている既存関数やマクロはここで定義されています。
要するに、絶対使うので思考停止で毎回書いておきましょう。

これ以外にもパラメータがありますが説明しません。
:import-fromと:shadowはまあ使わんけど使うかなあって感じなのでググっといてください。(丸投げ)

以下のサイトも参考にしてください。
CLHS:Macro DEFPACKAGE
お気楽 Common Lisp プログラミング入門:パッケージの基本的な使い方
【CommonLisp】別パッケージの関数と同名の関数を定義したい

補足(provide & in-package)
(provide :fizzbuzz)
(defpackage :fizzbuzz
  (:nicknames :fb)
  (:use :cl)
  (:export :fizzbuzz
           :create-fizzbuzz-list))
(in-package :fizzbuzz)

provideはC言語でいうところのインクルードガードみたいなもんです。
requireするならdefpackageする前に書いておきましょう。
(まあ後述の方法があるからなのか、あまり見かけないですね。)

in-packageは以下で定義するシンボルはすべて○○パッケージのもんだ!と宣言するやつです。
シェルで例えると、cdでディレクトリを移動してから作業するような感じです。

パッケージの取り込み(ASDF)

CommonLispの深淵ASDFです。
難しいです。意味不明です。関わりたくないです。解説したくないです。
ASDFはCommonLispというゲームのラスボス的存在です。
マクロなんかよりも何倍も難解であり、
これに関わりたくないからrequireで逃げていました。
ですが、
requireは死んだんだ。君もASDFと向き合わなければいけないんだ。
って感じなので説明します。

(先に言い訳。私はASDFとQuicklispの知識が十分でないです。そのようなガバガバ知識なので説明が間違っているかもしれません。難しいからね、しょうがないね。)

とりあえず、結論を先に書きます。

こうやって書くのだ!

fizzbuzz.lisp
(defpackage :fizzbuzz
  (:nicknames :fb)
  (:use :cl)
  (:export :fizzbuzz
           :create-fizzbuzz-list))
(in-package :fizzbuzz)

(defconstant +fizz+ 3)
(defconstant +buzz+ 5)

(defun fizzp (num)
  (zerop (mod num +fizz+)))

(defun buzzp (num)
  (zerop (mod num +buzz+)))

(defun fizzbuzz (num)
  (let ((fizzp (fizzp num))
        (buzzp (buzzp num)))
    (cond
      ((and fizzp buzzp) "FIZZBUZZ")
      (fizzp "FIZZ")
      (buzzp "BUZZ")
      (t num))))

(defun create-fizzbuzz-list (start end)
  (loop for i from start to end
        collect (fizzbuzz i)))
fizzbuzz.asd
(defsystem :fizzbuzz
  :version "1.0.0"
  :licence "WTFPL"
  :description "みんな大好きFizzBuzz"
  :author "mogamoga1337"
  :depends-on ()
  :components ((:file "fizzbuzz")))
main.lisp
(ql:quickload :fizzbuzz :silent t)

(defun main ()
  (format t "~{~a~%~}" (fizzbuzz:create-fizzbuzz-list 1 15)))

はい!できた!
asdファイルでfizzbuzzシステムを定義してから
ql:quickloadでfizzbuzzシステムを読み込んでいるよ!
defsystemのcomponentsでファイルを指定しているところがミソだね!

よーし、じゃあパパ実行しちゃうぞー

ros main.lisp
出力
System "fizzbuzz" not found

はい。無理ですね。fizzbuzzシステムってなんだよって感じのエラーがでます。
絶望です。辛いです。意味不明です。初見殺しです。

これはRoswell君がASDファイルを認識していないから起こるエラーです。
Roswell君にASDファイルを認識させる方法は何個かありますが、まずは最も単純な方法から説明します。

と…その前に章を変えましょうか。
(そのまま下の章を参照してください。余談は読みたい人だけ読んでください。)

余談

ql:quickload

ql:quickloadはシステムを読み込むための関数です。
システムというのはまあパッケージの集まりみたいなもんです。(多分)
(システムのことをプロジェクトとかライブラリとか言う人もいます。)
大抵の場合、エントリポイントとなるファイルの先頭でまとめて書いておきます。
本記事は紹介しませんが、RoswellScriptはその辺がうまくまとめられています。

;; 基本
(ql:quickload :fizzbuzz)

;; 文字列でもいい。
(ql:quickload "fizzbuzz")

;; これは何故か許されない。
;(ql:quickload #:fizzbuzz)

;; 成功メッセージやエラーを見たくない場合
(ql:quickload :fizzbuzz :silent t)

;; 複数システムをまとめて読み込みたい場合
(ql:quickload '(:fizzbuzz :hogehoge))
:(コロン)VS #:(シャープコロン)

defpackage内で指定するパッケージ名やシンボル名は:(コロン)もしくは#:(シャープコロン)を使うのが主流です。

;; :(コロン)こそが正義
(defpackage :fizzbuzz
  (:nicknames :fb)
  (:use :cl)
  (:export :fizzbuzz
           :create-fizzbuzz-list))
(in-package :fizzbuzz)

;; #:(シャープコロン)こそが正義
(defpackage #:fizzbuzz
  (:nicknames #:fb)
  (:use #:cl)
  (:export #:fizzbuzz
           #:create-fizzbuzz-list))
(in-package #:fizzbuzz)

;; 別に文字列でもいいが… 大文字でないとダメ
;; 入力がだるいので普通はしない。
(defpackage "FIZZBUZZ"
  (:nicknames "FB")
  (:use "CL")
  (:export "FIZZBUZZ"
           "CREATE-FIZZBUZZ-LIST"))
(in-package "FIZZBUZZ")

;; 実はただのシンボルでもよかったりする。
;; でも見かけない。
(defpackage fizzbuzz
  (:nicknames fb)
  (:use cl)
  (:export fizzbuzz
           create-fizzbuzz-list))
(in-package fizzbuzz)

:(コロン)はキーワードだというのはすでに説明しているので置いといて、
#:(シャープコロン)は何なのかというと、これはどこのパッケージにもインターンされていない(含まれていない)シンボルを定義するときに使うやつです。
正直、私にはdefpackage以外での使い道が分からないです。

CLHS:2.4.8.5 Sharpsign コロン

さて、defpackageにおいて:(コロン)と#:(シャープコロン)のどちらを使えばいいのか?という話ですが、現在だと#:(シャープコロン)派の勢力が強いです。
彼らの言い分だと:(コロン)だとキーワードパッケージにインターンされてメモリ上で無駄に長生きするから嫌。#:(シャープコロン)はインターンされないから即開放される。文字列は論外。的な感じでした。

個人的には入力が楽なので:(コロン)が好きです。

このような議論もありました。
(もともとは逆引きCommonLispさんのサイトで紹介されていましたが、今はプライバシーエラーが発生して見れないです。)

packages.lisp、そしてdepends-on

Lispエイリアンはパッケージをひとつのファイルでまとめて定義したりします。
これはする人もいれば、しない人もいます。
私はdefpackageするファイルと関数なりマクロなりを定義するファイルが一緒の方が管理しやすいのでそういうことはしないです。
でも、まあ一応説明しときます。

(この習慣ってどっかで明記されているの?)

こんな感じです。

fizzbuzz.asd
(defsystem :fizzbuzz
  :version "1.0.0"
  :licence "WTFPL"
  :description "みんな大好きFizzBuzz"
  :author "mogamoga1337"
  :depends-on ()
  :components
  ((:file "packages")
   (:file "constants" :depends-on ("packages"))
   (:file "fizzbuzz1" :depends-on ("packages" "constants"))
   (:file "fizzbuzz2" :depends-on ("fizzbuzz1"))))
packages.lisp
(defpackage :constants
  (:nicknames :const)
  (:use :cl)
  (:export :+fizz+
           :+buzz+))

(defpackage :fizzbuzz
  (:nicknames :fb)
  (:use :cl)
  (:export :fizzbuzz
           :create-fizzbuzz-list))
constants.lisp
(in-package :constants)

(defconstant +fizz+ 3)
(defconstant +buzz+ 5)
fizzbuzz1.lisp
(in-package :fizzbuzz)

(defun fizzp (num)
  (zerop (mod num const:+fizz+)))

(defun buzzp (num)
  (zerop (mod num const:+buzz+)))
fizzbuzz2.lisp
(in-package :fizzbuzz)

(defun fizzbuzz (num)
  (let ((fizzp (fizzp num))
        (buzzp (buzzp num)))
    (cond
      ((and fizzp buzzp) "FIZZBUZZ")
      (fizzp "FIZZ")
      (buzzp "BUZZ")
      (t num))))

(defun create-fizzbuzz-list (start end)
  (loop for i from start to end
        collect (fizzbuzz i)))

packages.lispでパッケージをまとめて定義しているのが分かると思います。
(packages.lispをpackage.lispという名称にする人もいます。そこは好みでしょう。)

:componentsの:fileの:depends-onでは依存しているファイルを指定しています。
依存ってなんだよって感じですが、そこはまあアレです。アレ。パッケージが定義されているとか、関数とかマクロが定義されているから先にロードしとく必要があるってことです。分かるでしょ?(丸投げ)

外部ライブラリが依存している場合は以下を参考にしてください。
外部ライブラリの管理

駄文

実は上記の例は:depends-onでファイルを指定しなくてもros main.lispで実行ができます。
ですが、その後に

constants.lisp
(in-package :constants)

(defconstant +fizz+ 4)
(defconstant +buzz+ 7)

みたいに編集して実行すると変更が反映されませんでした。
多分キャッシュみたいなやつが残っていると思います。(分からん。間違っているかも。)
:depends-onでファイルを指定してあげると変更が反映されました。
不思議だよね。

循環参照

AパッケージがBパッケージに依存していて、かつ
BパッケージがAパッケージに依存しているようなコードを書くとエラーが出ます。
昔、それで四苦八苦した思い出がありますがおそらく解決方法はないです。
同じパッケージにしてあげてください。しゃーなし。

ASDF参考リンク

ASDFとキーボード

キーボードを見ながらASDFと入力してください。
そしたら何かが見えてくるはずです。

ASDファイルを取り込む方法

前提として、ファイルなどは
パッケージの取り込み(ASDF)
の冒頭で定義しているものを使うことにします。

グローバル(ベタ置き)

~/.roswell/local-projects/
というディレクトリがあります。
そこの配下に適当にフォルダを作ってその中にasdファイルとasdファイルが参照しているファイルを配置してください。
(~/.roswell/lisp/quicklisp/local-projects/配下でもよいです。使い分けが分からん。)

今回の例ですと

~/.roswell/local-projects/
└── fizzbuzz/
    ├── fizzbuzz.asd
    └── fizzbuzz.lisp

てな感じです。

すると、どうでしょう。
どこからでもql:quickloadでfizzbuzzシステムがロード可能になります。

やったね!

ローカル(Qlot)

時にはバージョン管理とかの都合で、特定のディレクトリのみにシステムを閉じ込めたい場合があります。
そうなると前述の方法は使えません。

では、どうするのかというとQlotというツールを使います。

Qlotとは、JavaScriptでいうところのnpm
PHPでいうところのComposerみたいなもんです。
(あーちょっと違うかな。どちらかというとそれはQuicklispのほうかな。まあいいや。)
外部ライブラリの管理に関しては後の章で説明します。

私の恩師はQlotのことを
「Quicklispの機能を使ったプロジェクト単位のローカルライブラリインストーラ」
と言っていました。

まずはQlotをインストールします。

ros install qlot

インストール後、~/.roswell/bin/にqlotの実行ファイルができるのでパスを通してください。

以下のディレクトリ構成を想定して説明します。

hogehoge/
└── fizzbuzz/
    ├── main.lisp
    ├── fizzbuzz.asd
    └── fizzbuzz.lisp

まず、qlfileファイルを作ります。
今回は外部ライブラリと無縁なので中身は空でいいです。

hogehoge/
└── fizzbuzz/
    ├── qlfile
    ├── main.lisp
    ├── fizzbuzz.asd
    └── fizzbuzz.lisp

qlfileファイルを作ったらqlot installしてください。

qlot install

うまくいくと、いろいろとファイルが入っている.qlotフォルダが生成されます。

hogehoge/
└── fizzbuzz/
    ├── .qlot/
    ├── qlfile
    ├── main.lisp
    ├── fizzbuzz.asd
    └── fizzbuzz.lisp

なお、qlot installでソケットがどうのこうのでエラーが発生した人はdocker経由で試してみてください。

docker run --rm -t -v $PWD:/app fukamachi/qlot install

(これおま環かなあ…辛い。docker経由でエラーが起きないというのも不思議なんだよなあ。)

ここまで成功したら以下のコマンドでローカル的な実行ができます。

qlot exec ros main.lisp

やったね!

めんどくさいコードを書く方法

これをすることはないと思いますが、プログラム上からいじいじするとASDファイルを読み込めます。

外部ライブラリの管理

うまい説明方法が思いつかないんで実例形式で解説します。

前々から私が気になっていたcl-strという外部ライブラリを使ってみます。
これは貧弱すぎるCommonLispの文字列操作を拡張してくれる便利なライブラリです。

グローバル(Quicklisp)

グローバル的なインストールがしたいなら今まで通り、ql:quickloadで呼び出してあげればよいです。
自分の環境に対象のライブラリが存在しなければ自動でQuicklispのサーバーからライブラリをダウンロードし読み込んでくれます。
Quicklispのライブラリに登録されていないようなやつでしたら、先ほど説明したフォルダにGitなりで配置してあげてください。

main.lisp
(ql:quickload :str :silent t)

(defun main ()
  (format
   t 
   (str:replace-all "鈴木" "田所"
                    "インタビュアー:プログラミングとかはするの?~@
                    鈴木:やったことありますよ。~@
                    イン:どういう系統が好きなの?~@
                    鈴木:そうですねえ…~@
                    鈴木:やっぱり僕は、王道を征くLisp系ですか。~@
                    イン:あ、Lisp?難しいでしょ、Lisp?~@
                    鈴木:ピンキリですよね。でもね。~@
                    イン:じゃあ、CommonLispとかっていうのは?~@
                    鈴木:やりますねえ!~@
                    イン:やるんだ。~@
                    鈴木:やりますやります。~%")))
出力
インタビュアー:プログラミングとかはするの?
田所:やったことありますよ。
イン:どういう系統が好きなの?
田所:そうですねえ…
田所:やっぱり僕は、王道を征くLisp系ですか。
イン:あ、Lisp?難しいでしょ、Lisp?
田所:ピンキリですよね。でもね。
イン:じゃあ、CommonLispとかっていうのは?
田所:やりますねえ!
イン:やるんだ。
田所:やりますやります。

関係ないけど~@(チルダアット)なかなか便利だなあ。

アンインストールしたい場合はql:uninstall関数を使ってください。

(ql:uninstall :str)

(依存ライブラリが削除されていない気がするけど…気のせい?)

ローカル(Qlot)

一応事前に読んでおいてください。
ASDファイルを取り込む方法:ローカル(Qlot)

Qlotに関しては公式の解説が詳しいのでそっちを見たほうが絶対速いです。

でも説明します。
cl-strはQuicklispに登録されているライブラリなので以下のようにqlfileに記述します。

qlfile
ql str

仮にQuicklispに登録されていないがGitHubなどに公開されているとしたら、以下のように書けます。

qlfile
git str https://github.com/vindarel/cl-str.git

qlfileを作ったら、qlot installでインストールしましょう。

qlot install

ここまで説明すれば十分かな。
(qlot exec ros hoge.lispで実行するんだよ。ros hoge.lispじゃないよ。うっかり忘れちゃうから気を付けてね。一応言っておくけどREPLで動作確認したいときはqlot exec ros runだからね。)

ASDF

例えば個人的に作っているhogehogeシステムがcl-strに依存していたとします。
その場合、asdファイルの:depends-onで指定してあげれば個別にql:quickloadしなくても自動で取ってきてくれます。

こんな感じです。(適当)

hogehoge.asd
(defsystem :hogehoge
  :version "1.0.0"
  :licence "WTFPL"
  :description "ほげほげ"
  :author "mogamoga1337"
  :depends-on (:str) ; ここで依存ライブラリを指定する。
  :components
  ((:file "packages")
   (:file "hogehoge" :depends-on ("packages"))))
packages.lisp
(defpackage :hogehoge
  (:use :cl)
  (:export :yarimasunee))
hogehoge.lisp
(in-package :hogehoge)

(defun yarimasunee ()
  (format
   t 
   (str:replace-all "鈴木" "田所"
                    "インタビュアー:プログラミングとかはするの?~@
                    鈴木:やったことありますよ。~@
                    イン:どういう系統が好きなの?~@
                    鈴木:そうですねえ…~@
                    鈴木:やっぱり僕は、王道を征くLisp系ですか。~@
                    イン:あ、Lisp?難しいでしょ、Lisp?~@
                    鈴木:ピンキリですよね。でもね。~@
                    イン:じゃあ、CommonLispとかっていうのは?~@
                    鈴木:やりますねえ!~@
                    イン:やるんだ。~@
                    鈴木:やりますやります。~%")))
main.lisp
;(ql:quickload :str :silent t) ← これは不要、下のhogehogeで一緒に読み込んでくれるため。
(ql:quickload :hogehoge :silent t)

(defun main ()
  (hogehoge:yarimasunee))

その他 & ブレインダンプ & チラ裏

いろいろ好きに書きます。

ためになるサイト集

Lispの真実
Lisp: 良い知らせ、悪い知らせ、大成功への提言
A Road to Common Lisp (Common Lisp への道)
Lisp:よくある誤解
Lisp:よくある正解
The Lisp Curse(Lispの呪い)
コードウォッチ:関数型プログラミングの自惚れ問題

Lispがそんなにすごいなら ---If Lisp Is So Great---
PythonとLispの関係について
人気の言語を作るには ---Being Popular---
技術野郎の復讐---Revenge of the Nerds---
普通のやつらの上を行け ---Beating the Averages---
「技術野郎の復讐---Revenge of the Nerds---」への反響
ヘンな言語
百年の言語 --- The Hundred-Year Language
簡潔さは力なり---Succinctness is Power---
デザインとリサーチ ---Design and Research---
Rees Re: OO
なぜArcはとりたててオブジェクト指向でないのか---Why Arc Isn't Especially Object-Oriented---

Lisp:読み物

マンガで分かるLisp(Manga Guide to Lisp)
魔法言語 リリカル☆Lisp

Lisp武勇伝
ファイナルファンタジー7のプログラマーが明かす秘密とLispトークバトル、2月7日開催
クラッシュバンディクーはLispで作られた
NASA Programmer Remembers Debugging Lisp in Deep Space(NASAの遠隔デバッグ)
ガベージコレクション(Lispが発端)

Build Your Own Lisp(Lispの自作ガイド)
((Pythonで) 書く (Lisp) インタプリタ)
((Pythonで) 書く ((さらに良い) Lisp) インタプリタ)
A Lisp Interpreter Implemented in Conway's Game of Life(ライフゲームで実装されたLisp)

コンピュータ将棋プログラムをLISPで書く
Kandria(CLで実装されたSteamゲー)

JavaScript:The World's Most Misunderstood Programming Language(Cの皮を被ったLisp)

マッカーシーの91関数
健全なマクロ
M-expression
Greenspun's tenth rule

Why I Like CALL-WITH-* Style In Macros #

On Lisp
計算機プログラムの構造と解釈 第二版

Information Processing Language
CLHS:1.1.2 History
History of Lisp

LISP 1.5 Programmer's Manual
LISP Information and Resources

Current recommended libraries
Awesome Common Lisp(ありとあらゆるCLライブラリ集)
made-in-japan(日本人が作ったCLライブラリ集)

本当にLispはカッコが多い?
常用すべきCommon Lispライブラリ5選

Clojureをつくったわけ

Lispエイリアンの初出…らしい。
Casting SPELs in Lisp
Was the little green alien guy always the mascot of lisp or did it just start with Land of Lisp?

なお、よく見かけるLispエイリアンはLand of Lispの作者であるConrad Barski氏が生みの親です。
Conrad Barski
Public Domain Lisp Logo Set
LAND OF LISP OFFICIAL WEBSITE FOR THE BOOK

ジョーク集
Humor
Infrequently Asked Questions
Programming in the Cold War
プログラミング言語ヒエラルキー
Lispの名言が凄すぎる
不完全にしておよそ正しくないプログラミング言語小史

最新のネタを探したいならここ
PLANET LISP
Reddit:Lisp
Reddit:Lisp(日本)

独り言

CommonLispのことを「コモリス」って略してほしい。かわいいから。


Lispが宇宙人の技術で作られた言語っていうミーム(※)
初出が分からないんだよなあ。
Lispエイリアンは多分このミームの後で生まれたと思うんだけど。
いや、やっぱりLispエイリアンが先か?分からん。

※ このサイトのタイトルがそれ
Lisp - made with secret alien technology

metaタグのdescriptionの内容
Programming in Lisp is like playing with the primordial forces of the universe.
It feels like lightning between your fingertips. No other language even feels close.

↑ Glenn Ehrlich, Road to Lispからの引用だそう


あ…やっと…CommonLispから解放されたんやな…
やっぱLispは最高やな。みんなもLisp、やろう!俺もやったんだからさ。

6
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
4