Lisp Tutorial for Pythonista Day 6

1,844
-1

Published on

Lisp Tutorial最終回。コンパイラとかパッケージングとか、実際にツールを作ってう時に必須になりそうな部分を解説します。あと、おまけで賛否両論なloopマクロについてちょこっとまとめ。最後にお勧めの書籍とか紹介してからおしまいです。

Published in: Technology
0 Comments
2 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
1,844
On Slideshare
0
From Embeds
0
Number of Embeds
1
Actions
Shares
0
Downloads
0
Comments
0
Likes
2
Embeds 0
No embeds

No notes for slide

Lisp Tutorial for Pythonista Day 6

  1. 1. Lisp tutorial for Pythonista.Day #6 : Gleaning Ransui Iso Strategic Technology Group, X-Listing Co, Ltd.
  2. 2. 宿題は OK?
  3. 3. 今日は色々と紹介するよ!実際にツール作るときには必要になる系
  4. 4. 言語&ライブラリリファレンス 枕の下に常備しましょう
  5. 5. Common Lisp Reference● Common Lisp the Language, 2nd Edition ● 日本語訳本は高いけど Amazon で買えるよ ● 仕様書なので、はっきり言って超読みづらい ● その仕様が決定された背景の話とかも書いてあるので、読みづらさ の壁を突破すれば、けっこう楽しく読める ● 英語版ならオンラインで読める – http://www.cs.cmu.edu/Groups/AI/html/cltl/cltl2.html● Common Lisp Quick Reference ● もう持ってるよね!超便利! ● http://clqr.berlios.de/download.php ● 印刷 >  折り紙 > ステープラなう!
  6. 6. Common Lisp HyperSpec● HTML 形式のまとめられた CL の仕様書 ● http://www.lispworks.com/documentation/common­lisp.html ● LispWorks の中の人が作ってくれている ● 関数毎にちょっとしたサンプルコードが載っててこれが参考になる ● 英語だ! – 文学的な表現なんて無いから、怖がらずに挑戦してみて!● SLIME との連携が素晴らしい ● emerge hyperspec ● emerge w3m ● emerge emacs-w3m ●(setq common­lisp­hyperspec­root      (concat "file://" (expand­file­name "/usr/share/doc/hyperspec­7.0/HyperSpec/"))) ●(setq common­lisp­hyperspec­symbol­table      (expand­file­name "/usr/share/doc/hyperspec­7.0/HyperSpec/Data/Map_Sym.txt")) ●(setq browse­url­browser­function w3m­browse­url) ● ● C-c C-d H : カーソルがある部分について HyperSpec を索く
  7. 7. コンパイラとのお付き合い 実行効率 UP のための Tips
  8. 8. Common Lisp はコンパイラ言語!● REPL もあるし動的だしでインタプリタ感覚だけど ● SBCL の場合、 REPL で入力した式とかも、その場でコンパイルさ れて、実際にはネイティブコードを実行してる● 変数とか関数の型宣言もできる ● 型宣言をしない場合はコンパイラが型を推定してコンパイルする ● 型情報はプログラムの流れとかから分析するけど、よくわからない ときは Generic な処理をするルーチンを呼ぶようなコードになる ので実行効率が悪い ● 適切に型宣言すると、なかなか素敵なコードにコンパイルされる ● 型宣言しすぎるとコードの可読性が落ちることがあるので注意● アルゴリズムが一番重要 ● なんだかんだチューニングしても、最終的には効率のいいアルゴリ ズムでプログラムを書くことが一番大切
  9. 9. 型宣言の方法● declare を使う ●(declare (type  型名 変数名 1  変数名 2 ... )) ● ● 標準的な型名は Quick Reference の 30p 参照● 複合型の場合とか(defun print­ubyte8­array­elements (target­array)  (declare (type (array (unsigned­byte 8)) target­array))  (loop :for index :from 0 :below (length target­array) :do    (print (aref target­array index))))(print­ubyte8­array­elements   (make­array 3 :element­type (unsigned­byte 8)                 :initial­contents (1 2 3)))これは型エラーになる(print­ubyte8­array­elements (1 2 3))
  10. 10. 型宣言の効果● 特に型宣言していない場合(defun fact (x)  (do ((result 1 (* result x))       (x x (­ x 1)))     ((= x 1) result)))● 生成されるマシンコードを見ると;      917:       4C8D1C25CD020020 LEA R11, [#x200002CD]      ; GENERIC­*;      91F:       41FFD3           CALL R11;      922:       480F42E3         CMOVB RSP, RBX;      926:       488955F0         MOV [RBP­16], RDX;      92A:       488B55F8         MOV RDX, [RBP­8];      92E:       BF08000000       MOV EDI, 8;      933:       4C8D1C2556020020 LEA R11, [#x20000256]      ; GENERIC­­;      93B:       41FFD3           CALL R11;      93E:       480F42E3         CMOVB RSP, RBX;      942:       488B5DF0         MOV RBX, [RBP­16];      946:       488955F8         MOV [RBP­8], RDX;      94A: L1:   48895DF0         MOV [RBP­16], RBX;      94E:       488B55F8         MOV RDX, [RBP­8];      952:       BF08000000       MOV EDI, 8;      957:       488D0C25AF040020 LEA RCX, [#x200004AF]      ; GENERIC­=;      95F:       FFD1             CALL RCX;      961:       488B5DF0         MOV RBX, [RBP­16]
  11. 11. 型宣言の効果● 型宣言してみた場合(defun declared­fact (x)  (declare (type fixnum x))  (do ((result 1 (* result x)))      ((= x 1) result)    (declare (type integer result))    (decf x)));      1B1:       4C8D1C2590050020 LEA R11, [#x20000590]      ; ALLOC­SIGNED­BIGNUM­IN­RAX;      1B9:       41FFD3           CALL R11;      1BC: L1:   A807             TEST AL, 7;      1BE:       753C             JNE L3;      1C0:       488BD8           MOV RBX, RAX;      1C3:       48895DF8         MOV [RBP­8], RBX;      1C7:       488BD1           MOV RDX, RCX;      1CA:       488BFB           MOV RDI, RBX;      1CD:       4C8D1C25CD020020 LEA R11, [#x200002CD]      ; GENERIC­*;      1D5:       41FFD3           CALL R11;      1D8:       480F42E3         CMOVB RSP, RBX;      1DC:       488BCA           MOV RCX, RDX;      1DF:       488B5DF8         MOV RBX, [RBP­8];      1E3: L2:   4883FB08         CMP RBX, 8;      1E7:       75B7             JNE L0;      1E9:       488BD1           MOV RDX, RCX;      1EC:       488BE5           MOV RSP, RBP 掛け算の結果が bignum になるかもと推定してる!カコイイ!
  12. 12. 関数の型宣言● declaim を使って引数と戻り値の型を指定する(declaim (ftype (function ( 引数の型 1  引数の型 2 ... )  戻り値の型 )  関数名 )) ● 関数の戻り値の型が指定できるので、コンパイラはより良い型推論 ができるようになる。● 型宣言の例(declaim (ftype (function (fixnum fixnum) fixnum) fixnum­add))(defun fixnum­add (x y) (+ x y))(declaim (ftype (function (fixnum fixnum) (array fixnum)) make­fixnum­array))(defun make­fixnum­array (x y)  (make­array 2 :element­type fixnum :initial­contents (list x y)))
  13. 13. コンパイラへの指示● 最適化とかについての指示ができる●(declaim (optimize (compilation­speed 1)                    (debug 1)●                   (safety 1)                    (space 1)●                   (speed 1)) ● 数値は 0 〜 3 で指定する ● 数値が大きいほど重要であることを示す ● compilation-speed コンパイル速度 ● debug デバッグ情報の埋め込みっぷり ● safety 実行時型チェックとかのエラーチェックの厳密度 ● space 生成コードサイズと実行時メモリサイズを小さくする speed 実行速度
  14. 14. 実行可能ファイルの作り方● 事前準備 コードの先頭方面で (defvar *standalone­execute* t) とかしておいて、ケツで (if *standalone­execute*   (pushnew (lambda () (main) (quit)) sb­ext:*init­hooks*)   (progn (main) (quit))) としておく ( エントリポイントは main 関数っていう前提ね ) ● 普段は *standalone­execute* を nil にしておいて、プログラ ムが完成して実行形式を作りたくなったら t にする。
  15. 15. コンパイル用スクリプト● こんな shell script を作る #!/bin/bash sbcl ­­eval "(progn (load "${1}.lisp") (sb­ext:save­lisp­and­die "./ ${1}" :purify t :executable :t))" sbcc とか名前付けてパーミッションも +x しとく ● foo.lisp をコンパイルして実行可能ファイルを作るとき $ sbcc foo ● カレントディレクトリに foo という実行可能ファイルが出来る ● ./foo とやれは実行できる ● foo には Lisp 環境が全部詰まってるので超巨大 ● ldd ­v foo とかすればわかるが依存ライブラリはほとんどない – つまり SBCL がインスコされてないマシンでも実行可能だ。
  16. 16. パッケージとライブラリライブラリを作っていかないと実用には辛いわな
  17. 17. Common Lisp のパッケージ● Python で言うところのモジュールに近いけど… ● 名前空間の分離 – これは機能的にほぼ同じ – 明示的に export しなくちゃいけない – export してないシンボルも強制的に参照できる手段もあって… ● いわゆる import とかがメンドクサイ – ASDF を使うのが今のところ現実的 ● ライブラリとかコンポーネントを作りたい時には避けて通れない
  18. 18. ASDF でできること● 一定のルールでモジュールを定義する ● asd ファイル ● package.lisp ファイル ● 実際のプログラムが書かれてる lisp ファイル● ルールに従うことで得ることができるご利益 ● 自動コンパイル ● 簡単ロード ● 依存関係解決 gentoo で emerge sbcl してれば asdf は組み込まれた状態になっているので、特 になにか準備するとかの必要は無い。 ちなみに、今まで使ってきた QuickLisp も低レベル部分は asdf を使ってるので、 QuickLisp 経由でインストールしたモジュール asdf で単独ロードできる。
  19. 19. 超簡単な例● ライブラリ置き場のディレクトリを適当に作る ● mkdir ~/Temp/my­cl­lib● 環境変数をセット ● export CL_SOURCE_REGISTRY=~/Temp/my­cl­lib● 以下の3つのファイルを作成する ● helloworld.asd ● packages.lisp ● helloworld.lisp
  20. 20. helloworld.lisp● これがモジュールの本体 ●(in­package :helloworld)●(defun greeting (name) ●  (format t " こんにちわ ~a~%"    (clsql:sql  ●      (if (or (null name) (string= name "")) ●         " 名無しさん " name)))) ● 先頭の in-package でカレントパッケージを指定しておく ● その他の部分は普通に書けばいい ● 他のモジュールを使いたい場合、明示的にロードする必要は無い ● ASDF が依存関係を解決して自動ロードしてくれる なんか無理やり clsql に依存させてるけど、気にするな。これは練習だ。
  21. 21. packages.lisp● パッケージ名と依存関係を定義する(in­package :cl­user)(defpackage helloworld  (:use :cl :clsql)  (:export    :greeting)) ● 最初に in-package で :cl-user をカレントにしておく ● :use の部分で依存するパッケージを列挙しておく – cl パッケージへの依存設定を忘れないように! ● :export の部分で外部に公開する関数とか変数を列挙する
  22. 22. helloworld.asd● asdf に対するモジュール属性の定義 ●(defpackage :helloworld­system (:use :asdf))(in­package :helloworld­system)●(defsystem helloworld ●  :name "helloworld"  :author "Foo Bar <foo@example.com>" ●  :version "0.1" ●  :maintainer "Ham Spam <ham@example.com>"  :license "GNU GPL Version 2" ●  :components ((:file "packages")               (:file "helloworld" :depends­on ("packages"))) ●  :depends­on ("clsql"))    ● :components – モジュールを構成するファイル間の依存関係を定義する。コンパイル&ロード 順序に影響するのでちゃんと設定しよう。 ● :depends-on – 依存する外部モジュール名を列挙する
  23. 23. モジュールのロード● 3 点セットが出来たらロードしてみる(asdf:oos asdf:load­op helloworld)● うまく行けば下のように使えるようになる(helloworld:greeting "Foo Bar")(use­package :helloworld)(greeting "Foo Bar")
  24. 24. ライブラリを作るときのディレクトリ ● 例えば下のようにしておくといいかもdrwxr­xr­x 5 ransui strategy 120 Feb  1 12:51 ../drwxr­xr­x 2 ransui techteam 192 Feb 10 18:41 ASD/drwxr­xr­x 2 ransui techteam 176 Feb  1 12:47 xlis­bz2/drwxr­xr­x 2 ransui techteam 272 Feb 10 19:04 xlis­datetime/drwxr­xr­x 2 ransui techteam 272 Feb 10 18:15 xlis­dbi/drwxr­xr­x 2 ransui strategy 256 Feb  1 13:15 xlis­remoralog$ ls ­al ASDlrwxrwxrwx 1 ransui techteam  24 Feb 10 18:24 xlis­bz2.asd ­> ../xlis­bz2/xlis­bz2.asdlrwxrwxrwx 1 ransui techteam  34 Feb 10 18:41 xlis­datetime.asd ­> ../xlis­datetime/xlis­datetime.asdlrwxrwxrwx 1 ransui techteam  24 Feb 10 18:07 xlis­dbi.asd ­> ../xlis­dbi/xlis­dbi.asdlrwxrwxrwx 1 ransui techteam  36 Feb 10 16:50 xlis­remoralog.asd ­> ../xlis­remoralog/xlis­remoralog.asd ● ASD ディレクトリに各モジュールの asd ファイルへのシンボリッ クリンクをまとめておく ● こうしておくと CL_SOURCE_REGISTRY 環境変数に ASD ディレ クトリだけを指定しておけば、全部のモジュールが ASDF でロー ドしたりできるようになる。
  25. 25. loop マクロ 黒魔術扱いされることも多いけど実際のプログラミングで使いこなせば超便利
  26. 26. カウントしながら繰り返し● 最もよくあるパターンかも(loop :for  変数  [:from N]     [:to N]     [:by N]   〜 )                [:upfrom N]   [:upto N]                [:downfrom N] [:downto N]                              [:below N]                              [:above N] ● from : 始まりの値 省略時は 0 デクリメントの時は省略不可 ● to : 上限または下限の指定 ● by : 増分 省略時は 1 もしくは -1
  27. 27. 単純繰り返し● カウンタ変数が要らない場合(loop :repeat N  〜 ) ● (dotimes (x N) 〜 ) とかでもイイけど変数使うし、 declare ignore するのメンドクサイ時に。
  28. 28. コレクションに対して繰り返し● Python のシーケンスに対する繰り返しっぽい:for  変数  :in      リスト  [by  ステップ関数 ]   〜          :on      リスト          :across  ベクタ● hash-table に対しての繰り返し:for  変数  being the hash­keys   :in  ハッシュ  [using (hash­value  変数 )] 〜 )                    hash­values                     hash­key(loop :for key being the hash­keys in MAP using (hash­value value)  :collect (cons key value)) 
  29. 29. 変数を更新しながら繰り返し● 単純なカウントループじゃないときに:for  変数  =  初期化式  [then  更新式 ]  〜● 複数の変数を更新したい時は (loop :for x = 1 :then (+ x 1)●       :for y = 10 :then (­ y 1)  〜
  30. 30. ループ終了条件の指定● コレクションとかいじってる時によく使う(loop  :for element in ("hello" "end" "world")  :until (string= key "end")   〜)(loop  :for line = (read­line *standard­input* nil nil)     :then (read­line *standard­input* nil nil)  :while line 〜)
  31. 31. 値を集める● 色々な集約処理ができる :collect 式 [into  変数 ] :append :nconc :count :sum :maximize :minimize collect : 式の評価値をリストにまとめる append : 式の評価値をリストとみなし、結合する nconc : 破壊的にリストを結合する count : 式の評価値が真とみなせるものの数を数える sum : 式の評価値の和を取る maximize : 最大値を得る minimize : 最小値を得る
  32. 32. 条件分岐● ループ内で条件分岐できる :if 条件式 〜  :unlessCL­USER> (loop :for x :from 1 :to 10              :if (evenp x) :collect x)(2 4 6 8 10)
  33. 33. ループの中断● break みたいな機能(loop :named outer for y :from 0 :below 16  (loop :for x :from 0 :below 16     :do (if (null (do­something x y))             (return­from outer (list x y)))))  ● 名前をつけないループの名前は nil ● (return  式 ) とした場合は現在の名前の無いループから抜ける
  34. 34. ループ内変数● ループ内で有効なローカル変数を定義するCL­USER> (loop :with in­loop­var = 10            :for x :from 0 :to 9            :collect (/ x in­loop­var))(0 1/10 1/5 3/10 2/5 1/2 3/5 7/10 4/5 9/10)
  35. 35. ループ内に任意の処理を記述する● :do を使って書く ● ループをネストするときによく使う(defun make­maze­map (maze­image)  (let ((result ()))    (loop :for y :from 1 :to 6 :do       (loop :for x :from 1 :to 6 :do          (when (is­passage maze­image x y)            (let ((position (list x y nil))                  (movable­positions ()))              (loop :for (dx dy) :in ((0 ­1) (1 0) (0 1) (­1 0)) :do                 (when (is­passage maze­image (+ x dx) (+ y dy))                   (push (list (+ x dx) (+ y dy)) movable­positions)))              (push (list position movable­positions) result)))))    (reverse result)))
  36. 36. その他のトピック● マクロ ● Lisp を Lisp たらしめる機能 ● 簡単なツールとかの範囲では、知らなくてもあまり困らない。 ● なんだが「難しい」とか「超すごい」とか言われるけど、結局の S 式を弄りまわしてるだけ。基本は難しくもなんともない。 ● 使いどころが重要。何でもかんでもマクロってのは良くない。● 入出力系 ● 標準入出力的な物以外は、ほとんど拡張機能で処理系依存! ● なので実際に処理系のマニュアル読みながらやるしかない。 ● 差異を吸収するライブラリがいくつかあるので、それを使う● スレッド ● もう、処理系毎に違いすぎ… ● でも使いこなすと Lisp の関数的な性質との相性はイイ。
  37. 37. さらに勉強するためにこんないい加減なチュートリアルで満足せずに 次のステップへ進もう
  38. 38. お勧めの本とか● これまでの知識があれば、迷うことなく読める 実際に使えるツールをベースにしているのでかな りお勧め。最初はこれから始めるのがいい。 Lisp のパワーとマクロについ学べる 2冊目に マクロを極めたい人へ
  39. 39. お勧めの本とか 教科書として優秀。実践的な例は少ないけれど 基礎をしっかり学びたい人向け。 Common Lisp に限定していないけれど、 「 Lisp の心」を知るための良書。軽快な語り口だが 奥が深い。 Common Lisp の仕様書 内容的にちょっと古い部分もあるけれど、 本格的に Common Lisp を使うようになったときには 座右の書になるはず。値段が…
  40. 40. Happy Hackingwith Common Lisp !

×