tags : Lisp, Book

第I部 LISPは力なり

1章 さあLispを始めてみよう

CLISP のインストールについての章 特にメモなし

1.1 Lispの方言

二つのLispの物語 新しいLispたち スクリプティングに使われるLisp方言 ANSI Common Lisp

1.2 CLISPを始めよう

CLISPのインストール CLISPを起動する

1.3 この章で学んだこと

2章 はじめてのLispプログラム

2.1 数当てゲーム

2.2 Lispでグローバル変数を定義する

  • 変数smallとbigを定義する

    Lispではグローバル変数を トップレベル定義 と呼ぶ。

    新しいトップレベル定義は defparameter 関数で作る。

    トップレベル定義はアスタリスクで囲むのが慣習である。

    (defparameter *small* 1)
    (defparameter *big* 100)
  • グローバル変数を定義するもう一つの方法

    defparameter は上書き可能。 defvar は上書き不可能。

    (defparameter *foo* 5)
    (defparameter *foo* 6)
    *foo*
    (defvar *foo* 5)
    (defvar *foo* 6)
    *foo*

2.3 基本的なLispのエチケット

2.4 グローバル関数を定義する

  • guess-my-number関数の定義

    グローバル関数の定義は defun 関数を使う。

    ash 関数は、渡された数値を2進数で考え、ビットを右または左にシフトする。

    (defparameter *small* 1)
     
    (defparameter *big* 100)
     
    (defun guess-my-number ()
      (ash (+ *small* *big*) -1))
     
    (guess-my-number)
  • smallerとbigger関数の定義

    setf 関数を使って、グローバル変数の値を変えられる。

    (defun smaller()
      (setf *big* (1- (guess-my-number)))
      (guess-my-number))
    (defun bigger()
      (setf *small* (1+ (guess-my-number)))
      (guess-my-number))
  • start-over関数の定義

2.5 ローカル変数を定義する

ローカル変数は let を使う

(let ((a 5)
      (b 6))
   (+ a b))

2.6 ローカル関数の定義

ローカル関数は flet 関数を使う。

(flet ((f (n) (+ n 10))
       (g (n) (- n 3)))
    (g (f 5)))

ローカル関数の中で同じスコープ内の関数を使いたい場合は labels 関数を使う。

labels 関数は、ローカル関数から別のローカル関数を呼べるだけでなく、ローカル関数が自分自身を呼ぶ場合( 再帰 )にも使える。

(labels ((a (n) (+ n 5))
         (b (n) (+ (a n) 6)))
(b 10))
 

2.7 本章で学んだこと

3章 Lispの構文の世界を探検する

3.1 シンタックスとセマンティクス

3.2 Lispシンタックスの構成要素

  • シンボル

    アルファベット、数値、記号。

    大文字、小文字を区別しない。

  • 数値

    凄い!!!!↓

    (expt 53 530)

    除算は有理数を返す。

    (/ 4 6)
  • 文字列

    文字列を表示するには princ 関数を使う。

    (princ "sumisonic")

3.3 Lispはコードとデータをどう区別するか

  • コードモード

    フォームを解釈するモード。

    フォームとは、最初の要素が特別のコマンド(通常は関数名)になっているリスト。(題付き並び)

  • データモード

    フォームをクォートしてデータとして扱うモード。

3.4 Lispとリスト

  • コンスセル

    2つのデータをつなぎ合わせるには cons 関数を使う

    (cons 'chicken 'cat)
    (cons 'porg '(beef chicken))

    Lispではコンスセルの連なりとリストは全く同じもの。以下は同じデータになる。

    (pork beef chicken)
    (pork . (beef . (chicken . ())))
  • リストを扱う関数

    list 関数を使えば2つ以上のリストを作れる。以下は全て同じもの。

    (cons 'pork (cons 'beef (cons 'chicken ())))
    (list 'pork 'beef 'chicken)
    '(pork beef chicken)
  • ネストしたリスト

    (cadadr '((hoge hage huge) (one two three) duck))

3.5 本章で学んだこと

第II部 LISPは対称なり

4章 条件と判断

4.1 nilと()の対称性

  • 空とは偽なり

    空のリストは nil として扱う。

    (if '()
        'i-am-true
        'i-am-false)

    再帰を使うというのは、リストの先頭の要素を撮って何かして、リストの残りが空でなければそれを同じ関数に渡して処理させる ということ。

    (defun my-length (list)
      (if list
        (1+ (my-length (cdr list)))
        0))
    (my-length '(list with four symbols))
  • ()の四つの顔

    '(), (), 'nil, nil は全て同じ値で偽になる。

  • 4.2 条件分岐: Ifとその仲間たち
  • ifは一度に一つずつ
  • ifを越えて: whenとunless
  • 万能条件コマンドcond

     
    (defvar *arch-enemy* nil)
     
    (defun pudding-eater (person)
      (cond ((eq person 'henry) (setf *arch-enemy* 'stupid-lisp-alien)
                               '(curse you lisp alien - you ate my pudding))
            ((eq person 'johnny) (setf *arch-enemy* 'useless-old-johnny)
                               '(i hope you choked on my pudding johnny))
             (t '(why you eat my puddin stranger ?))))
     
    (pudding-eater 'johnny)
     
  • caseによる分岐

    (defun pudding-eater (person)
      (case person
        ((henry) (setf *arch-enemy* 'stupid-lisp-alien)
                 '(curse you lisp alien - you ate my pudding))
        ((johnny) (setf *arch-enemy* 'useless-old-johnny)
                 '(i hope you choked on my pudding johnny))
        (otherwise '(why you eat my pudding stranger))))

4.3 ちょっとした条件式のテクニック

  • 隠された条件分岐、andとorを使う

    and に与えられた式のうち偽になるものが見つかったら、以降評価せずすぐに偽を返す。

    or に与えられた式のうち真になるものが見つかったら、以降評価せずすぐに真を返す。

    ゆえに以下は全て同じ

    (if *file-modified*
        (if (ask-user-about-saving)
            (save-file)))
     
    (and *file-modified* (ask-user-about-saving) (save-file))
     
    (if (and *file-modified* (ask-user-about-saving))
             (save-file))
  • 真理以上のものを返す関数

    Common Lisp では nil 以外は全て真として扱われる。

4.4 比較関数: eq、equal、そしてもっと

シンボル同士は eq を使って比較すべし

シンボル同士の比較でなければ equal を使う

equal は2つのものが同型である、つまり「同じ見かけをしている」かどうかを教えてくれる

(equal (list 1 2 3) (list 1 2 3))
(equal '(1 2 3) (cons 1 (cons 2 (cons 3 ()))))
 

4.5 本章で学んだこと

5章 テキストゲームのエンジンを作る

5.1 魔法使いのアドベンチャー

  • このゲームの世界
  • 基本的な要求仕様
  • 連想リストを使って景色を描写する

    連想リストのことを association list、もしくは alist と呼ぶ。

    (defparameter *nodes*
      '((living-room (you are in the living-room. a wizard is snoring loudly on the couth.))
        (garden (you are in a beautiful garden. there is a well in front of you.))
        (attic (you are in the attic. there is a giant welding touch in corner.))))
    *NODES*
  • 情景を描写する

    連想リスト(alist)の中からキーをもとに要素を取り出すには assoc 関数を使う

     
     
    (assoc 'garden *nodes*)

    (GARDEN (YOU ARE IN A BEAUTIFUL GARDEN. THERE IS A WELL IN FRONT OF YOU.))

    (defun describe-location (location nodes)
      (cadr (assoc location nodes)))
    (defparameter *nodes*
      '((living-room (you are in the living-room. a wizard is snoring loudly on the couth.))
        (garden (you are in a beautiful garden. there is a well in front of you.))
        (attic (you are in the attic. there is a giant welding touch in corner.))))
    (defun describe-location (location nodes)
      (cadr (assoc location nodes)))
     
    (describe-location 'living-room *nodes*)

    (YOU ARE IN THE LIVING-ROOM. A WIZARD IS SNORING LOUDLY ON THE COUTH.)

    関数型プログラミングスタイル : 関数は引数か関数内で宣言された変数しか参照せず、また値を返す以外の動作をしない。

5.2 通り道を描写する

(defun describe-path (edge)
  `(there is a ,(caddr edge) goint ,(cadr edge) from here.))
  • 準クオートの仕組み

    準クォートとはクォートされたデータ内でコードを展開すること。sprintf にイメージが近い。

    準クォートを使うには、 バッククォート ` を使う。

    準クォートでデータモードに入った後、コードモードに戻す(アンクォートする)には コンマ , を使う

  • 複数の通り道を一度に描写する

    mapcar は引数に他の関数とリストを受け取って、リストの要素一つ一つについてそれを引数として受け取った関数を呼び出す。他の言語での map のようなもの。

    (mapcar #'sqrt '(1 2 3 4 5))

    (1.0 1.4142135 1.7320508 2.0 2.236068)

    関数を(他の関数の引数として渡すなど)値として扱う場合には function オペレータを使う。

    (mapcar (function car) '((foo bar) (baz qux)))
     

    (FOO BAZ)

    function オペレータの略記は #' である。

    append は複数のリストを連結する

    (append '(mary had) '(a) '(little lamb))

    (MARY HAD A LITTLE LAMB)

    apply に関数とリストを渡すと、あたかもそのリストの各要素を引数として関数を呼び出したかのように動作する。

    (apply #'append '((mary had) (a) (little lamb)))

    (MARY HAD A LITTLE LAMB)

    <<describe-path>>
    (defun describe-paths (location edges)
      (apply #'append (mapcar #'describe-path (cdr (location edges)))))

5.3 特定の場所にあるオブジェクトを描写する

  • 目に見えるオブジェクトをリストする
  • 見えるオブジェクトを描写する

5.4 全てを描写する

5.5 ゲーム世界を動き回る

find 関数はリストから与えられた要素を探す関数

(find 1 '(0 1 2 3 4))

1

関数の呼び出しの後ろに特別な引数を渡すことで使えるようになる組み込みの機能がある。

(find 'y '((5 x) (3 y) (7 z)) :key #'cadr)

(3 Y)

5.6 オブジェクトを手に取る

member 関数はリストの中に要素があるかどうかを検査するのに使える。 member は見つけた要素以降のリストを返す。

(member nil '(t nil t nil))

(NIL T NIL)

push はリストを保持している変数の先頭に新しい要素を付け加える

(defparameter *foo* '(1 2 3))
(push 7 *foo*)
*foo*

(7 1 2 3)

5.7 持っているものを調べる

5.8 本章で学んだこと

6章 世界とのインタフェース: Lispでのデータの読み書き

6.1 テキストの表示と読み込み

  • スクリーンへの表示

    prin1 は改行せずに引数を表示する

    (progn (prin1 "hoge")
           (prin1 "hage")
           (prin1 "huge")
           (prin1 "hooo"))

    read 関数はLispの動作を一旦止めて、ユーザがREPLに何かタイプするのを待つ

    (defun say-hello ()
      (print "Please type your name:")
      (let (name (read))
        (print "Nice to meet you")
        (print name))
      )
     
    (say-hello)
  • ユーザに挨拶しよう
  • printとreadから始める
  • 人に優しいデータの読み書き

    princ はエスケープコードを使わないで出力します。たとえば、文字列の場合は ” で括られずにそのまま出力されます。

    (princ "hello, world")

    cite : Common Lisp 入門

6.2 Lispにおけるコードとデータの対称性

プログラムコードとデータを同じデータ構造を使って扱うプログラミング言語は、 同図象性をもつ(homoiconic) と呼ばれる

'(+ 1 2) ;データモード

(+ 1 2)

(+ 1 2) ;コードモード

3

6.3 ゲームエンジンに専用のインタフェースを追加する

  • 専用のREPLの準備
  • 専用のread関数を書く
  • game-eval関数を書く
  • game-print関数を書く

6.4 さあこの素敵なゲームインタフェースを試してみよう

6.5 readとevalの危険について

6.6 本章で学んだこと

6.5章 lambda:とても大事な関数なので特別に章を分けて説明しよう

6.5.1 lambdaがすること

lambda の引数は評価されずに lambda に渡される。つまり lambda は本物の関数ではなく マクロ である。

6.5.2 lambdaがそんなに大事なわけ

6.5.3 本章で学んだこと

7章 単純なリストの先へ

7.1 奇妙なリスト

  • ドットリスト

    nil 以外の値でリストが終端されているものは ドットリスト と呼ばれる。

    (cons 1 (cons 2 3))

    (1 2 . 3)

  • 循環リスト

    以下の方法で無限に続くリスト ‘(1 2 3 1 2 3 1 2 3 …) が作れる

    (setf *print-circle* t)
    (defparameter foo (list 1 2 3))
    (setf (cdddr foo) foo)

    #1=(1 2 3 . #1#)

  • 連想リスト

    連想リストを変更するには push 関数を使う

    (defparameter *drink-order* '((bill . doubule-espresso)
                                  (lisa . small-drip-coffee)
                                  (john . medium-latte)))

    DRING-ORDER

    (assoc 'lisa *drink-order*)

    (LISA . SMALL-DRIP-COFFEE)

    (push '(lisa . large-mocha) *drink-order*)

    ((LISA . LARGE-MOCHA) (BILL . DOUBULE-ESPRESSO) (LISA . SMALL-DRIP-COFFEE) (JOHN . MEDIUM-LATTE))

7.2 複雑なデータを扱うには

  • 木構造のデータの可視化
  • グラフを可視化する

7.3 グラフを作る

  • DOTの情報を生成する

    substitute-if は、与えられたテスト関数の結果によって値を置き換える関数

    (substitute-if #\e #'digit-char-p "I am a l33t hack3r!")

    I am a leet hacker!

    (substitute-if 0 #'oddp '(1 2 3 4 5 6 7 8 9))

    (0 2 0 4 0 6 0 8 0)

    mapcmapcar の変種で、結果のリストを返さない。副作用のためだけに使用される。

  • DOTファイルを画像にする
  • グラフを画像にする

7.4 無向グラフを作る

7.5 本章で学んだこと

8章 親父のワンプスとは一味違う

8.1 グランド・セフト・ワンプス

8.2 コンジェスチョン・シティのエッジを定義する

  • ランダムなエッジの生成
  • loopコマンドでループしよう
  • 孤島を作らない

    set-difference は2つのリストを取り、最初のリストにあって2番目のリストに含まれない要素全てをリストにして返す関数

    (set-difference '(1 3 5 7) '(2 4 6 8))

    (7 5 3 1)

  • コンジェスチョン・シティのためのエッジリストを完成させる

    リストから重複した要素を取り除くには remove-duplicates 関数を使う。 値をテストするための関数を :test キーワードにて与えることができる。(デフォルトでは eql)

    (remove-duplicates '("abc" "cba" "abc") :test #'equal)

    (“cba” “abc”)

    intersection は、2つのリストで共有されている要素(積集合)を求める関数。

    (intersection '(1 3 5 7) '(2 4 6 8))

    NIL

    (intersection '(i 2 3 4) '(3 4 5 6))

    (4 3)

8.3 コンジェスチョン・シティのノードリストを作る

8.4 新しいゲームを始めるために、グランド・セフト・ワンプスを初期化する

8.5 シティのマップを描く

  • 部分的な知識からシティを描く
  • 既知の部分だけの地図を描く
  • 街を歩き回る

8.6 ワンプスを狩り出せ!

8.7 本章で学んだこと

9章 より進んだデータ型とジェネリックプログラミング

9.1 配列

  • 配列を使う

    新しく配列を作るには make-array コマンドを使う

    (make-array 3)

    #(0 0 0)

    配列から要素を取り出すには aref 関数を使う

    (defparameter x (make-array 3))
    (setf (aref x 2) 7)
    (aref x 2)

    7

  • ジェネリックなセッター
  • 配列とリスト

9.2 ハッシュテーブル

  • ハッシュテーブルを使う

    新しいハッシュテーブルは make-hash-table コマンドで作る

    (make-hash-table)

    #<HASH-TABLE :TEST EQL :COUNT 0 {1001FBA1A3}>

    キーを使って要素を取り出すには gethash 関数を使う

    (defparameter x (make-hash-table))
    (setf (gethash 'hoge x) 25)
    (gethash 'hoge x)

    25 T

  • 複数の値を返す
  • ハッシュテーブルの性能
  • グランド・セフト・ワンプスの性能をハッシュテーブルで改善する

9.3 構造体

  • 構造体を使う

    構造体を作るには defstruct コマンドを使う。

    (defstruct person name age favorite-color)

    PERSON

    構造体のインスタンスを作るには、構造体定義時に自動的に作られる make-{構造体名} コマンドを使う

    (defstruct person name age favorite-color)
    (defparameter *sumisonic* (make-person :name "sumisonic"
                                           :age 42
                                           :favorite-color "pink"))
    *sumisonic*

    #S(PERSON :NAME “sumisonic” :AGE 42 :FAVORITE-COLOR “pink”)

    構造体の属性の要素を取り出したり設定するには、構造体定義時に自動的に作られる {構造体名}-{属性名} コマンドを使う

    (defstruct person name age favorite-color)
    (defparameter *sumisonic* (make-person :name "sumisonic"
                                           :age 42
                                           :favorite-color "pink"))
    (setf (person-favorite-color *sumisonic*) "blue")
    (person-favorite-color *sumisonic*)

    blue

  • 構造体をいつ使うか

9.4 データをジェネリックに扱う

  • シーケンスを使う

    シーケンスのすべての要素を単一の値にするには reduce 関数を使う

    (reduce #'+ '(3 4 5 6))

    18

    reduce 関数の初期値を設定するには、キーワード引数 :initial-value を与える

    ; リストの中から最大の偶数を返す
    (reduce (lambda (best item)
              (if (and (evenp item) (> item best))
                  item
                  best))
            '(7 4 6 5 3 2)
            :initial-value 0)

    6

    map 関数は、mapcar のジェネリクス版。引数に返り値の型を取る。

    (map 'list
         (lambda (x)
           (if (eq x #\s)
               #\S
               x))
         "this is a string")

    (#\t #\h #\i #§ #\ #\i #§ #\ #\a #\ #§ #\t #\r #\i #\n #\g)

    (map 'string
         (lambda (x)
           (if (eq x #\s)
               #\S
               x))
         "this is a string")

    thiS iS a String

  • 型述語を使って自分でジェネリック関数を作る

    defmethod コマンドでジェネリクス関数を作れる

    (defmethod add ((a number) (b number))
      (+ a b))
     
    (defmethod add ((a list) (b list))
      (append a b))
     
    (print (add 3 4))
    (print (add '(a b) '(c d)))

9.5 オーク・バトル

  • プレーヤーとモンスターのグローバル変数
  • ゲームのメイン関数
  • プレーヤーを管理する関数
  • プレーヤーの攻撃に使う補助関数
  • モンスターを管理する関数
  • モンスターたち

    構造体の継承には、キーワード引数 :include を使う

    (defstruct monster age (health '10))
    (defstruct (orc (:include monster)) level)
    (make-orc)

    #S(ORC :AGE NIL :HEALTH 10 :LEVEL NIL)

  • 戦いだ!

9.6 本章で学んだこと

第III部 LISPはハックなり

loopとformat: Lispの怪しげな下町

10章 loopコマンドによるループ

10.1 loopマクロ

  • loopの使用例

    loop マクロ

    (loop for i
          below 5
          sum i)

    10

    loop を使って始点と終点を指定して数える

    (loop for i
          from 5
          to 10
          sum i)

    45

    loop を使ってリストの要素について繰り返す

    (loop for i
          in '(100 20 3)
          sum i)

    123

    loop を使って繰り返しの中に処理を記述する

    (loop for i
          below 5
          do (print i))

    loop を使って特定の条件が満たされたときだけ何かする

    (loop for i
          below 10
          when (oddp i)
          sum i)

    25

    loop を使ってループを途中で抜ける

    (loop for i
          from 0
          do (print i)
          when (= i 5)
          return 'hoge)

    HOGE

    loop を使って値を集めてリストにする

    (loop for i
          in '(2 3 4 5 6)
          collect (* i i))

    (4 9 16 25 36)

    loop コマンドの中に複数のforが現れた場合、それらは平行してチェックされ、どれか一つが値を使い尽くしたところで停止する

    (loop for x below 10
          for y below 5
          collect (+ x y))

    (0 2 4 6 8)

  • loopマクロの全てを知る

10.2 loopを使って進化ゲームを作ろう

  • マップに草を生やそう
  • シミュレーション世界の1日
  • 世界を描く
  • ユーザインタフェースを作る
  • 進化の様子をみてみよう
  • 進化の種明かし

10.3 本章で学んだこと

11章 format関数でテキストを表示する

11.1 format関数の呼び出し方

  • 出力先

    format の最初の引数の出力先 3つ

    明示的に出力ストリームを指定すると,そこに出力. t にすると標準出力 (*standard-output*) に出力. nil にするとストリームには出力せず,出力を返り値(文字列)として返す.

  • 制御文字列
  • 値引数

11.2 Lispの値を表示する制御シーケンス

format の制御シーケンス

~s は prin1 と同じ

(format t "I am printing ~s in the middle sentence." "foo")

~a は princ と同じ

(format t "I am printing ~a in the middle sentence." "foo")

11.3 数値を整形する制御シーケンス

  • 整数の整形
  • 浮動小数点数の整形

11.4 複数行出力

11.5 テキストを揃える

11.6 繰り返しの制御シーケンス

11.7 綺麗な表を作るクレージーな整形トリック

11.8 ロボットの襲撃!

11.9 本章で学んだこと

12章 ストリーム

12.1 ストリームの種類

  • リソースの種類による分類
  • 向きによる分類

12.2 ファイルの読み書き

12.3 ソケットを使う

  • ソケットアドレス
  • コネクション
  • ソケット上でメッセージを送る
  • 遊んだ後はお片付け

12.4 異端児の文字列ストリーム

  • 関数にストリームを渡す
  • 長い文字列を作る
  • コードの読みやすさとデバッグ

12.5 本章で学んだこと

13章 Webサーバを作ろう!

13.1 Common Lispでのエラー処理

  • コンディションを通知する
  • 自前のコンディションを作る

    独自のコンディション(例外)を作るには define-condition マクロを使う

    (define-condition foo ()
      ()
      (:report (lambda (condition stream)
                 (princ "Stop FOOing around, numbskull!" stream))))
     
    (error 'foo')

    FOO

  • コンディションを横取りする

    コンディションが通知されたときに、中断せず代わりに実行する処理を書くには、 handler-case コマンドを使う

    (define-condition foo ()
      ()
      (:report (lambda (condition stream)
                 (princ "Stop FOOing around, numbskull!" stream))))
     
    (defun bad-function ()
      (error 'foo))
     
    (handler-case (bad-function)
                 (foo () "somebody signaled foo!")
                 (bar () "somebody signaled bar!"))

    somebody signaled foo!

  • 予想外のコンディションからリソースを保護する

    コンディションが通知されても、必ず実行するコードを書くには unwind-protect コマンドを使う

    (unwind-protect (/ 1 0)
      (princ "ゼロ除算でエラーになるが、ここは必ず実行される"))

    ; in: UNWIND-PROTECT (/ 1 0) ; (/ 1 0) ; ; caught STYLE-WARNING: ; Lisp error during constant folding: ; arithmetic error DIVISION-BY-ZERO signalled ; Operation was (/ 1 0). ; ; compilation unit finished ; caught 1 STYLE-WARNING condition ゼロ除算でエラーになるが、ここは必ず実行される; Evaluation aborted on #<DIVISION-BY-ZERO {10023FCFC3}>.

13.2 ゼロからWebサーバを書く

  • Webサーバの仕組み
  • リクエストパラメータ
  • リクエストヘッダを解析する
  • 文字列ストリームを使ってget-headerをテストする
  • リクエストボディの解析
  • 最後の仕上げのサーバ関数

13.3 動的なWebサイトを作る

  • リクエストハンドラをテストする
  • Webサイトの立ち上げ

13.4 本章で学んだこと

13.5章 美しき哉 関数型プログラミング

第IV部 LISPは科学なり

14章 関数型プログラミングでLispをレベルアップ

特にメモなし

14.1 関数型プログラミングって何だ?

14.2 関数型スタイルで書かれたプログラムの分析

14.3 高階プログラミング

  • 命令型コードでのコード合成
  • 関数型スタイルを使う
  • 高階プログラミングによる救援

14.4 関数型プログラミングはなぜクレージーか

14.5 関数型プログラミングはなぜ素晴らしいか

  • 関数型プログラミングはバグを減らす
  • 関数型プログラミングは簡潔だ
  • 関数型プログラミングはエレガントだ

14.6 本章で学んだこと

15章 ダイス・オブ・ドゥーム:関数型スタイルでゲームを書こう

15.1 ダイス・オブ・ドゥームのルール

15.2 ダイス・オブ・ドゥームのゲーム例

15.3 ダイス・オブ・ドゥームの実装、バージョン1

  • いくつかのグローバル変数
  • ゲーム盤の表現
  • ゲームツリーの生成
  • 相手に手番を渡す
  • 攻撃の手を計算する
  • 隣接するマスを見つける
  • 攻撃
  • 補給
  • game-tree関数を試す
  • 人間対人間でダイス・オブ・ドゥームをプレイする

15.4 コンピュータによる対戦相手を作る

  • ミニマックスアルゴリズム
  • ミニマックスをコードにする
  • AIプレーヤーを使うゲームループ
  • 人間対コンピュータで対戦してみよう

15.5 ダイス・オブ・ドゥームを高速化する

  • クロージャ
  • メモ化

    変数の中で、lambda 式の中で参照されると、その変数が作られたフォームの外側でも生き続けることができる変数のことを レキシカル変数 と呼ぶ。 このように変数を捕捉することを、 クロージャ を作るという。

    関数が以前受け取ったのと同じ引数でまた呼ばれたら、結果をもう一度計算せずに、計算済みの結果を返すようにする。これを メモ化 と呼ぶ

    (defun my-func (num)
      num * 2)
     
    (let ((old-my-func (symbol-function 'my-func))
          (previous (make-hash-table)))
      (defun my-func (num)
        (or (gethash num previous)
            (setf (gethash num previous) (funcall old-my-func num)))))
     
    (my-func 2)

    2 2

  • 末尾呼び出し最適化

3x3のゲーム盤でのプレイ例

15.6 本章で学んだこと

16章 マクロの魔法

16.1 簡単なLispマクロ

(defmacro let1 (var val &body body)
  `(let ((,var ,val))
     ,@body))

LET1

(let1 foo (+ 2 3) (* foo foo))

25

  • マクロの展開

    通常の Lisp 関数は、その関数を含むプログラムを実行するときに走る。 一方 マクロ はプログラムが走る前、Lisp 環境でプログラムが読み込まれてコンパイルされる時点で走る。

  • マクロはどんなふうに変換されるか

    ,@ をプレフィックスとして付けると、括弧を1つ取り外して展開する。このことを スプライシング と呼ぶ。

    `(1 (list 2 3 4))

    (1 (LIST 2 3 4))

    `(1 ,@(list 2 3 4))

    (1 2 3 4)

    defmacro では &body キーワードを使って、残りのひとまとめにし可変の引数を扱えるようにできる

    (defmacro our-when (test &body hoge)
      `(if ,test
           (progn ,@hoge)))

    OUR-WHEN

    (our-when (= 1 1)
              (princ "true")
              (princ "hoo!!!"))
  • 簡単なマクロを使ってみる

    macroexpand コマンドを使えば、マクロがどのように展開されるか確かめることができる

    (defmacro let1 (var val &body body)
      `(let ((,var ,val))
         ,@body))
     
    (macroexpand '(let1 foo (+ 2 3)
                   (* foo foo)))

    (LET ((FOO (+ 2 3))) (* FOO FOO)) T

16.2 もっと複雑なマクロ

  • リストを分割するマクロ
  • マクロ中で式が繰り返し実行されるのを防ぐ
  • 変数捕捉を避ける
  • 再帰呼び出しマクロ

16.3 マクロの危険と代替案

16.4 本章で学んだこと

マクロ内で作る変数を、仕様として敢えてマクロ使用者からも見えるようにしているとき、そのマクロは アナフォリックマクロ と呼ばれる。

(defmacro aif (test then &optional else)
  `(let ((it ,test))
     (if it
         ,then
         ,else)))

AIF

(aif (+ 1 2)
     (princ it))

3

17章 ドメイン特化言語

マクロを使った実践

17.1 ドメインとは何か

17.2 SVGファイルを書き出す

タグマクロを使ってXMLとHTMLを生成する SVG特有のマクロと関数を作る もっと複雑なSVG画像を描く

17.3 魔法使いのアドベンチャーゲームに新たなコマンドを追加する

ゲームコマンドを直接定義する 完成した魔法使いのアドベンチャーゲームをプレーしよう

17.4 本章で学んだこと

18章 遅延プログラミング

遅延評価の実装 clojureでは標準で提供されているので飛ばした

18.1 Lispに遅延評価を足す

lazyコマンドとforceコマンドの作成 遅延リストライブラリを作る 通常のリストと遅延リストとの変換 遅延リストに対するマッピングと検索

18.2 ダイス・オブ・ドゥーム、バージョン2

18.3 大きなゲーム盤でAIを動かす

ゲーム木の刈り込み ヒューリスティクスを適用する 大きく勝つか小さく勝つか アルファ・ベータ法

18.4 本章で学んだこと

19章 ダイス・オブ・ドゥームに

グラフィカルなWebインタフェースをつける

19.1 ゲーム盤をSVGフォーマットで描画する

サイコロを描く マスを描く ゲーム盤を描く

19.2 Webサーバインタフェースを作る

リクエストハンドラの作成 このゲームWebサーバの制限 ゲームを初期化する 勝者を表示する 人間のプレーヤーの処理 コンピュータプレーヤーを処理する HTMLの中にSVGゲーム盤を描く

19.3 ダイス・オブ・ドゥーム、バージョン3をプレーする

19.4 本章で学んだこと

20章 ダイス・オブ・ドゥームをさらに面白く

20.1 プレーヤーの数を増やす

20.2 サイコロを振る

確率ノードを作る サイコロを実際に振る ゲームエンジンからサイコロを振るコードを呼び出す AIの改良

20.3 ダイス・オブ・ドゥームの補給ルールの改善

20.4 終わりに

tags : Lisp, Book

第I部 LISPは力なり

1章 さあLispを始めてみよう

1.1 Lispの方言

二つのLispの物語 新しいLispたち スクリプティングに使われるLisp方言 ANSI Common Lisp

1.2 CLISPを始めよう

CLISPのインストール CLISPを起動する

1.3 この章で学んだこと

2章 はじめてのLispプログラム

2.1 数当てゲーム

2.2 Lispでグローバル変数を定義する

変数smallとbigを定義する グローバル変数を定義するもう一つの方法

2.3 基本的なLispのエチケット

2.4 グローバル関数を定義する

guess-my-number関数の定義 smallerとbigger関数の定義 start-over関数の定義

2.5 ローカル変数を定義する

2.6 ローカル関数の定義

2.7 本章で学んだこと

3章 Lispの構文の世界を探検する

3.1 シンタックスとセマンティクス

3.2 Lispシンタックスの構成要素

シンボル 数値 文字列

3.3 Lispはコードとデータをどう区別するか

コードモード データモード

3.4 Lispとリスト

コンスセル リストを扱う関数 ネストしたリスト

3.5 本章で学んだこと

第II部 LISPは対称なり

4章 条件と判断

4.1 nilと()の対称性

空とは偽なり ()の四つの顔

4.2 条件分岐: Ifとその仲間たち

ifは一度に一つずつ ifを越えて: whenとunless 万能条件コマンドcond caseによる分岐

4.3 ちょっとした条件式のテクニック

隠された条件分岐、andとorを使う 真理以上のものを返す関数

4.4 比較関数: eq、equal、そしてもっと

4.5 本章で学んだこと

5章 テキストゲームのエンジンを作る

5.1 魔法使いのアドベンチャー

このゲームの世界 基本的な要求仕様 連想リストを使って景色を描写する 情景を描写する

5.2 通り道を描写する

準クオートの仕組み 複数の通り道を一度に描写する

5.3 特定の場所にあるオブジェクトを描写する

目に見えるオブジェクトをリストする 見えるオブジェクトを描写する

5.4 全てを描写する

5.5 ゲーム世界を動き回る

5.6 オブジェクトを手に取る

5.7 持っているものを調べる

5.8 本章で学んだこと

6章 世界とのインタフェース: Lispでのデータの読み書き

6.1 テキストの表示と読み込み

スクリーンへの表示 ユーザに挨拶しよう printとreadから始める 人に優しいデータの読み書き

6.2 Lispにおけるコードとデータの対称性

6.3 ゲームエンジンに専用のインタフェースを追加する

専用のREPLの準備 専用のread関数を書く game-eval関数を書く game-print関数を書く

6.4 さあこの素敵なゲームインタフェースを試してみよう

6.5 readとevalの危険について

6.6 本章で学んだこと

6.5章 lambda:とても大事な関数なので特別に章を分けて説明しよう

6.5.1 lambdaがすること

6.5.2 lambdaがそんなに大事なわけ

6.5.3 本章で学んだこと

7章 単純なリストの先へ

7.1 奇妙なリスト

ドットリスト 対 循環リスト 連想リスト

7.2 複雑なデータを扱うには

木構造のデータの可視化 グラフを可視化する

7.3 グラフを作る

DOTの情報を生成する DOTファイルを画像にする グラフを画像にする

7.4 無向グラフを作る

7.5 本章で学んだこと

8章 親父のワンプスとは一味違う

8.1 グランド・セフト・ワンプス

8.2 コンジェスチョン・シティのエッジを定義する

ランダムなエッジの生成 loopコマンドでループしよう 孤島を作らない コンジェスチョン・シティのためのエッジリストを完成させる

8.3 コンジェスチョン・シティのノードリストを作る

8.4 新しいゲームを始めるために、グランド・セフト・ワンプスを初期化する

8.5 シティのマップを描く

部分的な知識からシティを描く 既知の部分だけの地図を描く 街を歩き回る

8.6 ワンプスを狩り出せ!

8.7 本章で学んだこと

9章 より進んだデータ型とジェネリックプログラミング

9.1 配列

配列を使う ジェネリックなセッター 配列とリスト

9.2 ハッシュテーブル

ハッシュテーブルを使う 複数の値を返す ハッシュテーブルの性能 グランド・セフト・ワンプスの性能をハッシュテーブルで改善する

9.3 構造体

構造体を使う 構造体をいつ使うか

9.4 データをジェネリックに扱う

シーケンスを使う 型述語を使って自分でジェネリック関数を作る

9.5 オーク・バトル

プレーヤーとモンスターのグローバル変数 ゲームのメイン関数 プレーヤーを管理する関数 プレーヤーの攻撃に使う補助関数 モンスターを管理する関数 モンスターたち 戦いだ!

9.6 本章で学んだこと

第III部 LISPはハックなり

loopとformat: Lispの怪しげな下町

10章 loopコマンドによるループ

10.1 loopマクロ

loopの使用例 loopマクロの全てを知る

10.2 loopを使って進化ゲームを作ろう

マップに草を生やそう シミュレーション世界の1日 世界を描く ユーザインタフェースを作る 進化の様子をみてみよう 進化の種明かし

10.3 本章で学んだこと

11章 format関数でテキストを表示する

11.1 format関数の呼び出し方

出力先 制御文字列 値引数

11.2 Lispの値を表示する制御シーケンス

11.3 数値を整形する制御シーケンス

整数の整形 浮動小数点数の整形

11.4 複数行出力

11.5 テキストを揃える

11.6 繰り返しの制御シーケンス

11.7 綺麗な表を作るクレージーな整形トリック

11.8 ロボットの襲撃!

11.9 本章で学んだこと

12章 ストリーム

12.1 ストリームの種類

リソースの種類による分類 向きによる分類

12.2 ファイルの読み書き

12.3 ソケットを使う

ソケットアドレス コネクション ソケット上でメッセージを送る 遊んだ後はお片付け

12.4 異端児の文字列ストリーム

関数にストリームを渡す 長い文字列を作る コードの読みやすさとデバッグ

12.5 本章で学んだこと

13章 Webサーバを作ろう!

13.1 Common Lispでのエラー処理

コンディションを通知する 自前のコンディションを作る コンディションを横取りする 予想外のコンディションからリソースを保護する

13.2 ゼロからWebサーバを書く

Webサーバの仕組み リクエストパラメータ リクエストヘッダを解析する 文字列ストリームを使ってget-headerをテストする リクエストボディの解析 最後の仕上げのサーバ関数

13.3 動的なWebサイトを作る

リクエストハンドラをテストする Webサイトの立ち上げ

13.4 本章で学んだこと

13.5章 美しき哉 関数型プログラミング

第IV部 LISPは科学なり

14章 関数型プログラミングでLispをレベルアップ

14.1 関数型プログラミングって何だ?

14.2 関数型スタイルで書かれたプログラムの分析

14.3 高階プログラミング

命令型コードでのコード合成 関数型スタイルを使う 高階プログラミングによる救援

14.4 関数型プログラミングはなぜクレージーか

14.5 関数型プログラミングはなぜ素晴らしいか

関数型プログラミングはバグを減らす 関数型プログラミングは簡潔だ 関数型プログラミングはエレガントだ

14.6 本章で学んだこと

15章 ダイス・オブ・ドゥーム:関数型スタイルでゲームを書こう

15.1 ダイス・オブ・ドゥームのルール

15.2 ダイス・オブ・ドゥームのゲーム例

15.3 ダイス・オブ・ドゥームの実装、バージョン1

いくつかのグローバル変数 ゲーム盤の表現 ダイス・オブ・ドゥームのルールをゲームの他の部分から分離する ゲームツリーの生成 相手に手番を渡す 攻撃の手を計算する 隣接するマスを見つける 攻撃 補給 game-tree関数を試す 人間対人間でダイス・オブ・ドゥームをプレイする

15.4 コンピュータによる対戦相手を作る

ミニマックスアルゴリズム ミニマックスをコードにする AIプレーヤーを使うゲームループ 人間対コンピュータで対戦してみよう

15.5 ダイス・オブ・ドゥームを高速化する

クロージャ
メモ化
末尾呼び出し最適化

* 3x3のゲーム盤でのプレイ例

15.6 本章で学んだこと

16章 マクロの魔法

16.1 簡単なLispマクロ

マクロの展開 マクロはどんなふうに変換されるか 簡単なマクロを使ってみる

16.2 もっと複雑なマクロ

リストを分割するマクロ マクロ中で式が繰り返し実行されるのを防ぐ 変数捕捉を避ける 再帰呼び出しマクロ

16.3 マクロの危険と代替案

16.4 本章で学んだこと

17章 ドメイン特化言語

17.1 ドメインとは何か

17.2 SVGファイルを書き出す

タグマクロを使ってXMLとHTMLを生成する SVG特有のマクロと関数を作る もっと複雑なSVG画像を描く

17.3 魔法使いのアドベンチャーゲームに新たなコマンドを追加する

ゲームコマンドを直接定義する 完成した魔法使いのアドベンチャーゲームをプレーしよう

17.4 本章で学んだこと

18章 遅延プログラミング

18.1 Lispに遅延評価を足す

lazyコマンドとforceコマンドの作成 遅延リストライブラリを作る 通常のリストと遅延リストとの変換 遅延リストに対するマッピングと検索

18.2 ダイス・オブ・ドゥーム、バージョン2

18.3 大きなゲーム盤でAIを動かす

ゲーム木の刈り込み ヒューリスティクスを適用する 大きく勝つか小さく勝つか アルファ・ベータ法

18.4 本章で学んだこと

19章 ダイス・オブ・ドゥームに

グラフィカルなWebインタフェースをつける

19.1 ゲーム盤をSVGフォーマットで描画する

サイコロを描く マスを描く ゲーム盤を描く

19.2 Webサーバインタフェースを作る

リクエストハンドラの作成 このゲームWebサーバの制限 ゲームを初期化する 勝者を表示する 人間のプレーヤーの処理 コンピュータプレーヤーを処理する HTMLの中にSVGゲーム盤を描く

19.3 ダイス・オブ・ドゥーム、バージョン3をプレーする

19.4 本章で学んだこと

20章 ダイス・オブ・ドゥームをさらに面白く

20.1 プレーヤーの数を増やす

20.2 サイコロを振る

確率ノードを作る サイコロを実際に振る ゲームエンジンからサイコロを振るコードを呼び出す AIの改良

20.3 ダイス・オブ・ドゥームの補給ルールの改善

20.4 終わりに