Lisp tutorial for Pythonista.
Day #2 : The Basics.




                                                      Ransui Iso
                       Strategic Technology Group, X-Listing Co, Ltd.
今日はプログラムを書くための
  基礎部分を勉強します
前回の宿題は OK ですか?
いちおう Emacs + SLIME が動く前提です
なにはともあれまずは REPL
    Read, Eval, Print Loop の略語
Python なら「インタラクティブシェル」
Emacs 起動して M-x slime
META キーの設定無い人は [ESC] 押して [x]
ちゃんと起動するとこうなるはず
ぱいそん
>>> 1 + 2
>>> x = 1
>>> y = 2
>>> z = x * y
>>> z



りすぷ
CL­USER> (+ 1 2)
CL­USER> (setf x 1)
CL­USER> (setf y 1)
CL­USER> (setf z (+ x y))
CL­USER> z
なんか警告出てます!


; in: LAMBDA NIL
;     (SETF X 1)
; ==>
;   (SETQ X 1)
; 
; caught WARNING:
;   undefined variable: X
変数は宣言してから使うのが基本
    ルーズな Python とは大違いだよ!
管理大好きスーツ族への強力なアピールポイントだ!




     警告ガン無視でも、まあ問題は無いんだが気持ち悪いので宣言しとけ。
変数宣言の前に名前空間の話
Python と似てる部分もあるし違う部分もある
Common Lisp の名前空間

●   大域は package という単位が基本になっている
     ●   概念的には Python のモジュールに近い
     ●   Python のモジュールより厳密に定義するのでお手軽じゃない

●   何も指定しない時は CL­USER がデフォルト
     ●   cl パッケージに Common Lisp の基本機能が入っている
     ●   イメージとしては
          cl­user.py の最初で
          from cl import * してある感覚。

●   もちろんローカル変数とかもちゃんとあるよ
     ●   関数引数とか、 let フォームとか色々ある
     ●   レキシカルスコープなので、基本部分は Python 感覚で OK 。
パッケージ変数の宣言

●   defvar と defparameter
      ●   どちらもパッケージ変数を宣言する
      ●   宣言するときには初期値を与える

●   defvar
      ●   再初期化されない

●   defparameter
      ●   再初期化される



    パッケージ変数という用語は一般的じゃない。 Python 風な名前で呼んで
    みてるだけ。ほんとは「スペシャル変数」と言う。実は深い意味もあるん
    だけど、今は無視しちゃう。
再初期化される?されない?

●   実験してみりゃ一目瞭然
CL­USER> (defvar *foo* nil)
*FOO*
CL­USER> (defparameter *bar* nil)
*BAR*
CL­USER> (setf *foo* 1)
1
CL­USER> (setf *bar* 2)
2
CL­USER> (defvar *foo* nil)
*FOO*
CL­USER> *foo*
1
CL­USER> (defparameter *bar* nil)
*BAR*
CL­USER> *bar*
NIL

    パッケージが再読み込みされた時とかに挙動が変わるよ!
CL のパッケージは取扱いが面倒
なので、今のところは CL-USER 一辺倒でいく

  ライブラリとか作るようになったら
    defpackage とか使うよ
ソースをファイルに書いて実行する

●   C­x 5 2 で新フレームを開いて
●   新しいフレームで C­x C­f hello.lisp とかし
    て新規バッファを開く
●   コード書く
(defun hello (name)
  (format t "Hello ~a~%" name))

(hello "World")

●   C­x C­s でセーブ
●   C­c C­k で REPL 環境にコンパイルしてロード
スクリプトっぽく実行する
ぱいそん
def hello(name):
    print("Hello %s" % name)

def main():
    hello("World")

if __name__ == "__main__":
    main()

りすぷ
(defun hello (name)
    (format t "Hello ~a~%" name))

(defun main()
    (hello "World"))

(eval­when (:compile­toplevel :load­toplevel :execute)
    (main)
    (quit))
eval­when は結構使い方が難しい。コンパイル、ロード、実行のタイミングとかは
Lisp 処理系の動きをイメージしないといけない部分があるからね。
コマンドラインから起動する

●   実は処理系依存なのよ
     ●   SBCL の場合は下のようにする



$ sbcl ­­script hello.lisp
Hello World
$
お待たせしました
プログラム作成のお時間です
すごく教科書的でアレなんですが
     「単語数え」
 プログラムを書いてみませう
慣れ親しんだ Python だと
                  あー、簡単のために単語は空白文字で区切られてるものとしてます。
def main()
    counter = dict()
    for line in open(sys.argv[1], "r"):
        for word in line.strip().split():
            index_word = word.lower()
            if index_word in counter:
                counter[index_word] += 1
            else:
                counter[index_word] = 1

   for (word, num) in counter.items():
       print("%20s : %4d" % (word, num))

if __name__ == "__main__":
    main()




                   まぁ、瞬殺なわけです
             これを Common Lisp で書くとどうなるか
単語数えプログラムを書くために

●   ファイル入出力
     ●   ファイルのオープン
     ●   パスの操作

●   文字列操作
     ●   スペースとかのデリミタでのフィールド分解

●   数え上げ
     ●   Python で言う所の辞書の使い方

●   その他
     ●   コマンドライン引数の扱いとか
とりあえず辞書から攻め込む
  先週の復習からはじめよう
ぱいそん
d = dict()
d["Hello"] = "World"
d["Hello"] → "World"
d[1] = 2
len(d) → 2
for (key, value) in d.items():
    print("%s : %s" % (key, value))


りすぷ
(setf d (make-hash-table :test #'equal))
(setf (gethash "Hello" d) "World)
(gethash "Hello" d) → "World"
(setf (gethash 1 d) 2)
(hash-table-count d) → 2
(maphash #'(lambda (key value)
             (princ (format nil "~a : ~a~%" key value))) d)
まぁそのままでもイイんだが
もちっと親しみやすくしたいよね!
辞書っぽくアクセスするようにしてみる
(define­condition key­error (error) 
    ((text :initarg :text :reader text)))

(defun make­dict ()
    (make­hash­table :test #'equal))

(defun set­item (dict key value)
  (setf (gethash key dict) value))

(defun get­item (dict key)
  (multiple­value­bind (value has­key) (gethash key dict)
    (if (not has­key)
        (error 'key­error :text (format nil "KeyError : ~a" key))
        value)))

(defun has­key (dict key)
  (multiple­value­bind (value has­key) (gethash key dict)
    (declare (ignore value))
    has­key))

(defun items (dict)
  (let ((result nil))
    (maphash #'(lambda (key value)
                 (setf result (cons (cons key value) result))) dict)
    result))
なんか、色々と新しいの出た!
細かい部分とか技とかは後日のお楽しみとして
  ポイントになる部分をザックリと解説
define-condition

●   Python で言う所の例外に近い
      ●   厳密には違うけどイメージとしてはこんなかんじ

class KeyError(Exception):
    def __init__(self, text):
        Exception.__init__(self, text)

                    KeyError は組み込みの例外だが、これはあくまで例だ。気にするな。




●   Common Lisp では「コンディション」と言う
      ●   「例外」と呼ばない点に注意
      ●   例外的な事象への対応以外にも色々と使える
      ●   Python の try 〜 except よりもずっと強力
eq, eql, equal, equalp

●       オブジェクトの比較関数だよ
         ●   オブジェクトの同一性と、値としての同値ってのは別腹
         ●   Python の is と == 演算子の違いに似てる

    –   eq     : メモリ上で同一オブジェクトかどうかをチェック

    –   eql    : 数値 or 文字の場合は同値か?それ以外は eq する

    –   equal : 値の構造が同一か?内部構造も再帰的にチェック

    –   equalp :equal よりも緩い判定。詳細は CLtL2 参照
ハッシュテーブルの生成部分

●   キーの比較関数を指定して生成してる
    (make­hash­table :test #'equal)

       ●   #'equal という表記は (function equal) の略記法
       ●   略記法があるってことは、今後もいっぱいでてくる予感
       ●   ちなみに比較関数指定を省略したときは #'eql がデフォルト

●   equal を指定している訳
       ●   今回は文字列をハッシュのキーにする
       ●   文字列は (eql "Hello" "Hello") → nil なのだ

       ●   ちなみに純粋な文字列比較関数の string= を使ってもいい
            –   その場合、この hash-table のキーはマジで文字列限定になる。
setf ってなんだ?

●   これは違和感ないはず
(setf x 10)


●   で、これは?
(setf (gethash key dict) value)


●   そもそも gethash 関数って
(gethash "Hello" dict) → "World"

     ●   みたいに値を取り出す関数なんじゃねーの?
     ●   つーことは、 "Hello" とか、出てきた値になんか代入すんの?
setf には値を入れる場所を指定する

●   ぶっちゃけ、ポインタです
       ●    setf は関数じゃないんです。マクロなんです。つーことは引数は
             評価される前の状態で setf に渡されてるということ
       ●    第一引数は「値を入れる場所」として解釈される

    dict
    Hello             (gethash "Hello" dict)
    foo
                      setf マクロと組み合わせてポインタ
    bar               として振る舞う関数には制限がある
                      詳しくは「 CLtL2 C7.2: 汎変数」を
                      参照のこと
多値関数

●   複数の値を返す関数
      ●   Python でタプル返すのとはちょっと違う。
def foo(x):
    return ((True if x % 2 else False), x + 1)

(is_odd, next) = foo(3) # OK
is_odd = foo(3)         # is_odd は単純にタプルを指すだけ



(defun foo(x)
    (values 
        (if (= (mod x 2) 1) t nil)
        (+ x 1)))

(multiple­value­bind (is_odd next_x) (foo 3)  ; 多値を受け取るための形式
    (cons is_odd next_x))

(setf is_odd (foo 3))                         ; 最初の値だけが採用される
gethash 関数の戻り値

●   2 値の多値関数
       ●   第一値: Key に対応する Value, Key が存在しない場合は nil
       ●   第二値: Key が存在した場合は t, 存在しない場合は nil
(defvar dict (make­hash­table :test #'equal))
(setf (gethash "foo" dict) nil)

       ●   上のとき、 (gethash "foo" dict) → nil
             foo というキーが登録されていないから nil なのか?
             foo というキーに対応している値が nil なのか?

            –
(defun has­key (dict key)
  (multiple­value­bind (value has­key) (gethash key dict)
    (declare (ignore value))
    has­key))

       ●   多値関数の戻り値を受け取っても使わない場合は「変数使ってない
            が大丈夫か?」とコンパイラが五月蝿いので declare を使って
            「大丈夫だ。問題ない。」と伝えておくと吉。
if フォーム

●   分岐の基本なんだがイマイチな部分もある
(if ( 条件テスト式 ) (then 節 ) (else 節 ))

      ●   then 節と else 節はブロックじゃない。 1 個の式しか書けない !
           –   しょうがないので progn とか let とか使ってブロック化する。

      ●   Python で言う所の elif が無い!
           –   ネストして頑張る。
           –   cond マクロを使う。
           –   case マクロを使う。

      ●   Python と違って if は " 文 " じゃない!
           –   値を返せるよ!戻り値は then 節とか else 節の式の評価値になる
let とローカル変数

●   正確には「レキシカル変数」と言う
      ●   呼称はまあいい。要するに let の範囲内のローカル変数だ
CL­USER> (let ((x 10) (y 20))
           (print x)
           (print y)
           (let ((x "Hello"))
             (print x)
             (print y)))

      ●   実行結果は予想のとおり。
      ●   let と関数定義を組み合わせたりと色々な技がある
      ●   let 以外にもレキシカル変数を取り扱うフォームは色々ある
lambda 式

●   関数の実体・無名関数
     ●   関数を使い捨てしたいときとかに良く使う
     ●   let と組み合わせてクロージャとか作ったりもする
(setf (symbol­function 'foo) #'(lambda (x) (+ x 1)))
                         ||
(defun foo(x) (+ x 1))


foo
value
func                (function (lambda (x) (+ x 1)))
prop             Common Lisp のシンボルは値用と関数用の 2 つのスロットを持っ
                 ているのだ!こういうタイプの Lisp を Lisp-2 という。ちなみに
                 Scheme は値と関数のスロットは分かれていない Lisp-1 。
                 Python も Lisp-1 風。
文字列いってみよう!
文字列って実用では重要
でも何故か多くの Lisp 本とかでの解説は少なめ
Lisp の文字列

●   実体は文字型の 1 次元配列
     ●   Common Lisp は文字型が存在する。文字型はエンコードとは独立
          するように設計されている。が、まぁモゴモゴ…
          –   C の char とは違う。 Java の char に似た立ち位置と思えばいい。

          –   文字をリテラルとして書く場合は #A とか #Newline とか書く。

          –   Python には文字型は無い。 1 文字の文字列として扱うか、エンコードされた整数コード
                として扱っている


     ●   リテラルで書く場合は "Hello World" のようにダブルクォート
          で囲んで書く。 'Hello' のようにシングルクオートはダメ

     ●   Common Lisp の文字列は mutable に振る舞う。取扱い注意。
文字列が mutable なので

●   こんなことができちゃいます
(defvar s1 "Hello World")
(defvar s2 s1)
(eq s1 s2)
(setf (char s 5) #­)
s1
s2
(eq s1 s2)


     ●   REPL 環境で実験してみて!
     ●   シンボル s1 と s2 は「同一の」文字列を参照している
     ●   setf はやっぱりポインタ操作でしょ?
     ●   メモリ上の文字列を直接書換えてるイメージなわけですよ。
文字列の操作

●   文字の配列なので配列の操作関数が使える
      ●   さらに配列はシーケンスの一種なのでシーケンス操作関数が使える
      ●   なので、文字列特有の解説が少ないのかも
      ●   文字列用に特別に準備された関数もいくつかある
ぱいそん
s = "Hello World"
s[3]
s[3:5]
s = s.strip()
s = s.lower()

りすぷ
(defvar s "Hello World")
(char s 3)
(subseq s 3 5)
(setf s (string­trim '(#Space #Tab #Newline) s))
(setf s (string­downcase s))
split を作る
(defmacro while (test­exp &body body)
  `(do () ((not ,test­exp)) ,@body))

(defun string­split (target­str &key (separators '(#Space #Tab)))
  (let ((result nil)
        (startpos 0)
        (curpos 0)
        (endpos (length target­str)))

    (while (< curpos endpos)
      (if (member (char target­str curpos) separators)
          (progn
            (setf result (cons (subseq target­str startpos curpos) result))
            (setf startpos (+ curpos 1))))

      (setf curpos (+ curpos 1)))

    (setf result (cons (subseq target­str startpos endpos) result))
    (nreverse result)))

                                         loop マクロ使って格好良くもできるけど分かり易さ重視のベタ実装。
使い方                                      にしても、ちょっと不格好過ぎ。コンパイル結果は以外といい。


CL­USER> (string­split "Hello World foo bar  波浪ワールド ")
("Hello" "World" "foo" "bar" " 波浪ワールド ")
関数のキーワード引数

●   Python と同じようにキーワード引数が使えるよ
      ●   関数の引数部分に &key というマークに続けて書く
      ●   デフォルト値を与える&与えないの選択可

ぱいそん
def foo(x, y, z=100):
    return x + y + z

foo(10, 20)
foo(10, 20, z=50)

りすぷ
(defun foo (x y &key (z 100))
    (+ x y z))

(foo 10 20)
(foo 10 20 :z 50)
cons 関数                     リストは cons セルで構成されている。
 (X Y)            (X . Y)
                            セルの左側を car
                            セルの右側を cdr

                            と呼ぶ。長大な薀蓄を聞きたいのでなけれ
                            ば、なんで left, right とかじゃないのかと
  X       Y        X    Y   いう質問はしないこと。



(cons 1 '(a b))
                       cons 関数は新しい cons セルを用意して、第一引
                       数を car 部に第二引数を cdr 部にセットする。

                       これを繰り返せば、どんな複雑なリストの構造も作
                       成できる。

  1                    まぁ実際は cons だけでは面倒くさいので色々と便
                       利関数は用意されている

                       リストのあれこれ tips については後日っす。
      a       b
cons をもう少し
L1                        L2


        X       Y                1       2


     (cons l1 (cons l2 nil)) → ((x y) (1 2))




        X       Y        1       2
setf をリストに使うときの注意

●   リストに対して setf してみると…
CL­USER> (defvar lst '(a b c))
CL­USER> (setf (cdr lst) '(1 2))
CL­USER> lst

      ●   上の結果はどうなるか?

lst


              A            B       C



                                       1   2
setf をリストに使う時の注意

 ●    新しいセル作らずに無理やりポインタ書換え!
 CL­USER> (defvar lst '(a b c))
 CL­USER> (setf (cdr lst) '(1 2))
 CL­USER> lst



lst
                      (cdr lst) の位置
                      を強制的に書換えた!
             A                         B        C


                                      これらのオブジェクトはもう参照
                                      できない!いずれ GC の餌食にな
                                      る
                  1            2
次はファイルの読み込み
結構簡単よ!
ぱいそん
with open("/tmp/foobar.txt", "r") as input_file:
   for line in input_file:
       print line

りすぷ
(with­open­file (input­file "/tmp/foobar.txt" :direction :input)
    (loop for line = (read­line input­file)
        while line
        do (princ (format nil "~a~%" line)) ))

       ●   loop マクロは相変わらず魔法だけど何となく分かるでしょ?
       ●   format 関数は超便利。後日まとめて機能を紹介するですよ
コマンドライン引数の取得

●   *posix­argv* というグローバル変数に入ってる
(defun main()
  (dolist (arg *posix­argv*)
    (princ (format nil "~a~%" arg))))

(eval­when (:compile­toplevel :load­toplevel :execute)
  (main)
  (quit))

$ sbcl ­­script argtest.lisp foo bar baz



       ●   dolist フォームの使い方。上の例で十分に分かるかと。
課題演習
「単語数え」プログラム
  もう作れるよね?

Lisp tutorial for Pythonista : Day 2

  • 1.
    Lisp tutorial forPythonista. Day #2 : The Basics. Ransui Iso Strategic Technology Group, X-Listing Co, Ltd.
  • 2.
  • 3.
    前回の宿題は OK ですか? いちおうEmacs + SLIME が動く前提です
  • 4.
    なにはともあれまずは REPL Read, Eval, Print Loop の略語 Python なら「インタラクティブシェル」
  • 5.
    Emacs 起動して M-xslime META キーの設定無い人は [ESC] 押して [x]
  • 6.
  • 7.
  • 8.
  • 9.
    変数は宣言してから使うのが基本 ルーズな Python とは大違いだよ! 管理大好きスーツ族への強力なアピールポイントだ! 警告ガン無視でも、まあ問題は無いんだが気持ち悪いので宣言しとけ。
  • 10.
  • 11.
    Common Lisp の名前空間 ● 大域は package という単位が基本になっている ● 概念的には Python のモジュールに近い ● Python のモジュールより厳密に定義するのでお手軽じゃない ● 何も指定しない時は CL­USER がデフォルト ● cl パッケージに Common Lisp の基本機能が入っている ● イメージとしては cl­user.py の最初で from cl import * してある感覚。 ● もちろんローカル変数とかもちゃんとあるよ ● 関数引数とか、 let フォームとか色々ある ● レキシカルスコープなので、基本部分は Python 感覚で OK 。
  • 12.
    パッケージ変数の宣言 ● defvar と defparameter ● どちらもパッケージ変数を宣言する ● 宣言するときには初期値を与える ● defvar ● 再初期化されない ● defparameter ● 再初期化される パッケージ変数という用語は一般的じゃない。 Python 風な名前で呼んで みてるだけ。ほんとは「スペシャル変数」と言う。実は深い意味もあるん だけど、今は無視しちゃう。
  • 13.
    再初期化される?されない? ● 実験してみりゃ一目瞭然 CL­USER> (defvar *foo* nil) *FOO* CL­USER> (defparameter *bar* nil) *BAR* CL­USER> (setf *foo* 1) 1 CL­USER> (setf *bar* 2) 2 CL­USER> (defvar *foo* nil) *FOO* CL­USER> *foo* 1 CL­USER> (defparameter *bar* nil) *BAR* CL­USER> *bar* NIL パッケージが再読み込みされた時とかに挙動が変わるよ!
  • 14.
    CL のパッケージは取扱いが面倒 なので、今のところは CL-USER一辺倒でいく ライブラリとか作るようになったら defpackage とか使うよ
  • 15.
    ソースをファイルに書いて実行する ● C­x 5 2 で新フレームを開いて ● 新しいフレームで C­x C­f hello.lisp とかし て新規バッファを開く ● コード書く (defun hello (name)   (format t "Hello ~a~%" name)) (hello "World") ● C­x C­s でセーブ ● C­c C­k で REPL 環境にコンパイルしてロード
  • 16.
    スクリプトっぽく実行する ぱいそん def hello(name): print("Hello %s" % name) def main(): hello("World") if __name__ == "__main__": main() りすぷ (defun hello (name) (format t "Hello ~a~%" name)) (defun main() (hello "World")) (eval­when (:compile­toplevel :load­toplevel :execute) (main) (quit)) eval­when は結構使い方が難しい。コンパイル、ロード、実行のタイミングとかは Lisp 処理系の動きをイメージしないといけない部分があるからね。
  • 17.
    コマンドラインから起動する ● 実は処理系依存なのよ ● SBCL の場合は下のようにする $ sbcl ­­script hello.lisp Hello World $
  • 18.
  • 19.
    すごく教科書的でアレなんですが 「単語数え」 プログラムを書いてみませう
  • 20.
    慣れ親しんだ Python だと あー、簡単のために単語は空白文字で区切られてるものとしてます。 def main() counter = dict() for line in open(sys.argv[1], "r"): for word in line.strip().split(): index_word = word.lower() if index_word in counter: counter[index_word] += 1 else: counter[index_word] = 1 for (word, num) in counter.items(): print("%20s : %4d" % (word, num)) if __name__ == "__main__": main() まぁ、瞬殺なわけです これを Common Lisp で書くとどうなるか
  • 21.
    単語数えプログラムを書くために ● ファイル入出力 ● ファイルのオープン ● パスの操作 ● 文字列操作 ● スペースとかのデリミタでのフィールド分解 ● 数え上げ ● Python で言う所の辞書の使い方 ● その他 ● コマンドライン引数の扱いとか
  • 22.
  • 23.
    ぱいそん d = dict() d["Hello"]= "World" d["Hello"] → "World" d[1] = 2 len(d) → 2 for (key, value) in d.items(): print("%s : %s" % (key, value)) りすぷ (setf d (make-hash-table :test #'equal)) (setf (gethash "Hello" d) "World) (gethash "Hello" d) → "World" (setf (gethash 1 d) 2) (hash-table-count d) → 2 (maphash #'(lambda (key value) (princ (format nil "~a : ~a~%" key value))) d)
  • 24.
  • 25.
    辞書っぽくアクセスするようにしてみる (define­condition key­error (error)  ((text :initarg :text :reader text))) (defun make­dict ()     (make­hash­table :test #'equal)) (defun set­item (dict key value)   (setf (gethash key dict) value)) (defun get­item (dict key)   (multiple­value­bind (value has­key) (gethash key dict)     (if (not has­key)         (error 'key­error :text (format nil "KeyError : ~a" key))         value))) (defun has­key (dict key)   (multiple­value­bind (value has­key) (gethash key dict)     (declare (ignore value))     has­key)) (defun items (dict)   (let ((result nil))     (maphash #'(lambda (key value)                  (setf result (cons (cons key value) result))) dict)     result))
  • 26.
  • 27.
    define-condition ● Python で言う所の例外に近い ● 厳密には違うけどイメージとしてはこんなかんじ class KeyError(Exception): def __init__(self, text): Exception.__init__(self, text) KeyError は組み込みの例外だが、これはあくまで例だ。気にするな。 ● Common Lisp では「コンディション」と言う ● 「例外」と呼ばない点に注意 ● 例外的な事象への対応以外にも色々と使える ● Python の try 〜 except よりもずっと強力
  • 28.
    eq, eql, equal,equalp ● オブジェクトの比較関数だよ ● オブジェクトの同一性と、値としての同値ってのは別腹 ● Python の is と == 演算子の違いに似てる – eq : メモリ上で同一オブジェクトかどうかをチェック – eql : 数値 or 文字の場合は同値か?それ以外は eq する – equal : 値の構造が同一か?内部構造も再帰的にチェック – equalp :equal よりも緩い判定。詳細は CLtL2 参照
  • 29.
    ハッシュテーブルの生成部分 ● キーの比較関数を指定して生成してる (make­hash­table :test #'equal) ● #'equal という表記は (function equal) の略記法 ● 略記法があるってことは、今後もいっぱいでてくる予感 ● ちなみに比較関数指定を省略したときは #'eql がデフォルト ● equal を指定している訳 ● 今回は文字列をハッシュのキーにする ● 文字列は (eql "Hello" "Hello") → nil なのだ ● ちなみに純粋な文字列比較関数の string= を使ってもいい – その場合、この hash-table のキーはマジで文字列限定になる。
  • 30.
    setf ってなんだ? ● これは違和感ないはず (setf x 10) ● で、これは? (setf (gethash key dict) value) ● そもそも gethash 関数って (gethash "Hello" dict) → "World" ● みたいに値を取り出す関数なんじゃねーの? ● つーことは、 "Hello" とか、出てきた値になんか代入すんの?
  • 31.
    setf には値を入れる場所を指定する ● ぶっちゃけ、ポインタです ● setf は関数じゃないんです。マクロなんです。つーことは引数は 評価される前の状態で setf に渡されてるということ ● 第一引数は「値を入れる場所」として解釈される dict Hello (gethash "Hello" dict) foo setf マクロと組み合わせてポインタ bar として振る舞う関数には制限がある 詳しくは「 CLtL2 C7.2: 汎変数」を 参照のこと
  • 32.
    多値関数 ● 複数の値を返す関数 ● Python でタプル返すのとはちょっと違う。 def foo(x): return ((True if x % 2 else False), x + 1) (is_odd, next) = foo(3) # OK is_odd = foo(3)         # is_odd は単純にタプルを指すだけ (defun foo(x) (values  (if (= (mod x 2) 1) t nil) (+ x 1))) (multiple­value­bind (is_odd next_x) (foo 3)  ; 多値を受け取るための形式 (cons is_odd next_x)) (setf is_odd (foo 3))                         ; 最初の値だけが採用される
  • 33.
    gethash 関数の戻り値 ● 2 値の多値関数 ● 第一値: Key に対応する Value, Key が存在しない場合は nil ● 第二値: Key が存在した場合は t, 存在しない場合は nil (defvar dict (make­hash­table :test #'equal)) (setf (gethash "foo" dict) nil) ● 上のとき、 (gethash "foo" dict) → nil foo というキーが登録されていないから nil なのか? foo というキーに対応している値が nil なのか? – (defun has­key (dict key)   (multiple­value­bind (value has­key) (gethash key dict)     (declare (ignore value))     has­key)) ● 多値関数の戻り値を受け取っても使わない場合は「変数使ってない が大丈夫か?」とコンパイラが五月蝿いので declare を使って 「大丈夫だ。問題ない。」と伝えておくと吉。
  • 34.
    if フォーム ● 分岐の基本なんだがイマイチな部分もある (if ( 条件テスト式 ) (then 節 ) (else 節 )) ● then 節と else 節はブロックじゃない。 1 個の式しか書けない ! – しょうがないので progn とか let とか使ってブロック化する。 ● Python で言う所の elif が無い! – ネストして頑張る。 – cond マクロを使う。 – case マクロを使う。 ● Python と違って if は " 文 " じゃない! – 値を返せるよ!戻り値は then 節とか else 節の式の評価値になる
  • 35.
    let とローカル変数 ● 正確には「レキシカル変数」と言う ● 呼称はまあいい。要するに let の範囲内のローカル変数だ CL­USER> (let ((x 10) (y 20))            (print x)            (print y)            (let ((x "Hello"))              (print x)              (print y))) ● 実行結果は予想のとおり。 ● let と関数定義を組み合わせたりと色々な技がある ● let 以外にもレキシカル変数を取り扱うフォームは色々ある
  • 36.
    lambda 式 ● 関数の実体・無名関数 ● 関数を使い捨てしたいときとかに良く使う ● let と組み合わせてクロージャとか作ったりもする (setf (symbol­function 'foo) #'(lambda (x) (+ x 1))) || (defun foo(x) (+ x 1)) foo value func (function (lambda (x) (+ x 1))) prop Common Lisp のシンボルは値用と関数用の 2 つのスロットを持っ ているのだ!こういうタイプの Lisp を Lisp-2 という。ちなみに Scheme は値と関数のスロットは分かれていない Lisp-1 。 Python も Lisp-1 風。
  • 37.
  • 38.
  • 39.
    Lisp の文字列 ● 実体は文字型の 1 次元配列 ● Common Lisp は文字型が存在する。文字型はエンコードとは独立 するように設計されている。が、まぁモゴモゴ… – C の char とは違う。 Java の char に似た立ち位置と思えばいい。 – 文字をリテラルとして書く場合は #A とか #Newline とか書く。 – Python には文字型は無い。 1 文字の文字列として扱うか、エンコードされた整数コード として扱っている ● リテラルで書く場合は "Hello World" のようにダブルクォート で囲んで書く。 'Hello' のようにシングルクオートはダメ ● Common Lisp の文字列は mutable に振る舞う。取扱い注意。
  • 40.
    文字列が mutable なので ● こんなことができちゃいます (defvar s1 "Hello World") (defvar s2 s1) (eq s1 s2) (setf (char s 5) #­) s1 s2 (eq s1 s2) ● REPL 環境で実験してみて! ● シンボル s1 と s2 は「同一の」文字列を参照している ● setf はやっぱりポインタ操作でしょ? ● メモリ上の文字列を直接書換えてるイメージなわけですよ。
  • 41.
    文字列の操作 ● 文字の配列なので配列の操作関数が使える ● さらに配列はシーケンスの一種なのでシーケンス操作関数が使える ● なので、文字列特有の解説が少ないのかも ● 文字列用に特別に準備された関数もいくつかある ぱいそん s = "Hello World" s[3] s[3:5] s = s.strip() s = s.lower() りすぷ (defvar s "Hello World") (char s 3) (subseq s 3 5) (setf s (string­trim '(#Space #Tab #Newline) s)) (setf s (string­downcase s))
  • 42.
    split を作る (defmacro while (test­exp &body body)   `(do () ((not ,test­exp)) ,@body)) (defun string­split (target­str &key (separators '(#Space #Tab)))   (let ((result nil)         (startpos 0)         (curpos 0)         (endpos (length target­str)))     (while (< curpos endpos)       (if (member (char target­str curpos) separators)           (progn             (setf result (cons (subseq target­str startpos curpos) result))             (setf startpos (+ curpos 1))))       (setf curpos (+ curpos 1)))     (setf result (cons (subseq target­str startpos endpos) result))     (nreverse result))) loop マクロ使って格好良くもできるけど分かり易さ重視のベタ実装。 使い方 にしても、ちょっと不格好過ぎ。コンパイル結果は以外といい。 CL­USER> (string­split "Hello World foo bar  波浪ワールド ") ("Hello" "World" "foo" "bar" " 波浪ワールド ")
  • 43.
    関数のキーワード引数 ● Python と同じようにキーワード引数が使えるよ ● 関数の引数部分に &key というマークに続けて書く ● デフォルト値を与える&与えないの選択可 ぱいそん def foo(x, y, z=100): return x + y + z foo(10, 20) foo(10, 20, z=50) りすぷ (defun foo (x y &key (z 100))     (+ x y z)) (foo 10 20) (foo 10 20 :z 50)
  • 44.
    cons 関数 リストは cons セルで構成されている。 (X Y) (X . Y) セルの左側を car セルの右側を cdr と呼ぶ。長大な薀蓄を聞きたいのでなけれ ば、なんで left, right とかじゃないのかと X Y X Y いう質問はしないこと。 (cons 1 '(a b)) cons 関数は新しい cons セルを用意して、第一引 数を car 部に第二引数を cdr 部にセットする。 これを繰り返せば、どんな複雑なリストの構造も作 成できる。 1 まぁ実際は cons だけでは面倒くさいので色々と便 利関数は用意されている リストのあれこれ tips については後日っす。 a b
  • 45.
    cons をもう少し L1 L2 X Y 1 2 (cons l1 (cons l2 nil)) → ((x y) (1 2)) X Y 1 2
  • 46.
    setf をリストに使うときの注意 ● リストに対して setf してみると… CL­USER> (defvar lst '(a b c)) CL­USER> (setf (cdr lst) '(1 2)) CL­USER> lst ● 上の結果はどうなるか? lst A B C 1 2
  • 47.
    setf をリストに使う時の注意 ● 新しいセル作らずに無理やりポインタ書換え! CL­USER> (defvar lst '(a b c)) CL­USER> (setf (cdr lst) '(1 2)) CL­USER> lst lst (cdr lst) の位置 を強制的に書換えた! A B C これらのオブジェクトはもう参照 できない!いずれ GC の餌食にな る 1 2
  • 48.
  • 49.
    結構簡単よ! ぱいそん with open("/tmp/foobar.txt", "r") as input_file: for line in input_file: print line りすぷ (with­open­file (input­file "/tmp/foobar.txt" :direction :input) (loop for line = (read­line input­file) while line do (princ (format nil "~a~%" line)) )) ● loop マクロは相変わらず魔法だけど何となく分かるでしょ? ● format 関数は超便利。後日まとめて機能を紹介するですよ
  • 50.
    コマンドライン引数の取得 ● *posix­argv* というグローバル変数に入ってる (defun main()   (dolist (arg *posix­argv*)     (princ (format nil "~a~%" arg)))) (eval­when (:compile­toplevel :load­toplevel :execute)   (main)   (quit)) $ sbcl ­­script argtest.lisp foo bar baz ● dolist フォームの使い方。上の例で十分に分かるかと。
  • 51.