Contenu connexe
Similaire à Lisp Tutorial for Pythonista Day 6 (20)
Lisp Tutorial for Pythonista Day 6
- 1. Lisp tutorial for Pythonista.
Day #6 : Gleaning
Ransui Iso
Strategic Technology Group, X-Listing Co, Ltd.
- 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. Common Lisp HyperSpec
● HTML 形式のまとめられた CL の仕様書
● http://www.lispworks.com/documentation/commonlisp.html
● LispWorks の中の人が作ってくれている
● 関数毎にちょっとしたサンプルコードが載っててこれが参考になる
● 英語だ!
– 文学的な表現なんて無いから、怖がらずに挑戦してみて!
● SLIME との連携が素晴らしい
● emerge hyperspec
● emerge w3m
● emerge emacs-w3m
●
(setq commonlisphyperspecroot
(concat "file://" (expandfilename "/usr/share/doc/hyperspec7.0/HyperSpec/")))
●
(setq commonlisphyperspecsymboltable
(expandfilename "/usr/share/doc/hyperspec7.0/HyperSpec/Data/Map_Sym.txt"))
●
(setq browseurlbrowserfunction 'w3mbrowseurl)
●
● C-c C-d H : カーソルがある部分について HyperSpec を索く
- 8. Common Lisp はコンパイラ言語!
● REPL もあるし動的だしでインタプリタ感覚だけど
● SBCL の場合、 REPL で入力した式とかも、その場でコンパイルさ
れて、実際にはネイティブコードを実行してる
● 変数とか関数の型宣言もできる
● 型宣言をしない場合はコンパイラが型を推定してコンパイルする
● 型情報はプログラムの流れとかから分析するけど、よくわからない
ときは Generic な処理をするルーチンを呼ぶようなコードになる
ので実行効率が悪い
● 適切に型宣言すると、なかなか素敵なコードにコンパイルされる
● 型宣言しすぎるとコードの可読性が落ちることがあるので注意
● アルゴリズムが一番重要
● なんだかんだチューニングしても、最終的には効率のいいアルゴリ
ズムでプログラムを書くことが一番大切
- 9. 型宣言の方法
● declare を使う
●
(declare (type 型名 変数名 1 変数名 2 ... ))
●
● 標準的な型名は Quick Reference の 30p 参照
● 複合型の場合とか
(defun printubyte8arrayelements (targetarray)
(declare (type (array (unsignedbyte 8)) targetarray))
(loop :for index :from 0 :below (length targetarray) :do
(print (aref targetarray index))))
(printubyte8arrayelements
(makearray 3 :elementtype '(unsignedbyte 8)
:initialcontents '(1 2 3)))
これは型エラーになる
(printubyte8arrayelements '(1 2 3))
- 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 [RBP16], RDX
; 92A: 488B55F8 MOV RDX, [RBP8]
; 92E: BF08000000 MOV EDI, 8
; 933: 4C8D1C2556020020 LEA R11, [#x20000256] ; GENERIC
; 93B: 41FFD3 CALL R11
; 93E: 480F42E3 CMOVB RSP, RBX
; 942: 488B5DF0 MOV RBX, [RBP16]
; 946: 488955F8 MOV [RBP8], RDX
; 94A: L1: 48895DF0 MOV [RBP16], RBX
; 94E: 488B55F8 MOV RDX, [RBP8]
; 952: BF08000000 MOV EDI, 8
; 957: 488D0C25AF040020 LEA RCX, [#x200004AF] ; GENERIC=
; 95F: FFD1 CALL RCX
; 961: 488B5DF0 MOV RBX, [RBP16]
- 11. 型宣言の効果
● 型宣言してみた場合
(defun declaredfact (x)
(declare (type fixnum x))
(do ((result 1 (* result x)))
((= x 1) result)
(declare (type integer result))
(decf x)))
; 1B1: 4C8D1C2590050020 LEA R11, [#x20000590] ; ALLOCSIGNEDBIGNUMINRAX
; 1B9: 41FFD3 CALL R11
; 1BC: L1: A807 TEST AL, 7
; 1BE: 753C JNE L3
; 1C0: 488BD8 MOV RBX, RAX
; 1C3: 48895DF8 MOV [RBP8], 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, [RBP8]
; 1E3: L2: 4883FB08 CMP RBX, 8
; 1E7: 75B7 JNE L0
; 1E9: 488BD1 MOV RDX, RCX
; 1EC: 488BE5 MOV RSP, RBP
掛け算の結果が bignum になるかもと推定してる!カコイイ!
- 12. 関数の型宣言
● declaim を使って引数と戻り値の型を指定する
(declaim (ftype (function ( 引数の型 1 引数の型 2 ... ) 戻り値の型 ) 関数名 ))
● 関数の戻り値の型が指定できるので、コンパイラはより良い型推論
ができるようになる。
● 型宣言の例
(declaim (ftype (function (fixnum fixnum) fixnum) fixnumadd))
(defun fixnumadd (x y) (+ x y))
(declaim (ftype (function (fixnum fixnum) (array fixnum)) makefixnumarray))
(defun makefixnumarray (x y)
(makearray 2 :elementtype 'fixnum :initialcontents (list x y)))
- 13. コンパイラへの指示
● 最適化とかについての指示ができる
●(declaim (optimize (compilationspeed 1)
(debug 1)
● (safety 1)
(space 1)
● (speed 1))
● 数値は 0 〜 3 で指定する
● 数値が大きいほど重要であることを示す
●
compilation-speed コンパイル速度
● debug デバッグ情報の埋め込みっぷり
● safety 実行時型チェックとかのエラーチェックの厳密度
●
space 生成コードサイズと実行時メモリサイズを小さくする
speed 実行速度
- 14. 実行可能ファイルの作り方
● 事前準備
コードの先頭方面で
(defvar *standaloneexecute* t)
とかしておいて、ケツで
(if *standaloneexecute*
(pushnew (lambda () (main) (quit)) sbext:*inithooks*)
(progn (main) (quit)))
としておく ( エントリポイントは main 関数っていう前提ね )
● 普段は *standaloneexecute* を nil にしておいて、プログラ
ムが完成して実行形式を作りたくなったら t にする。
- 15. コンパイル用スクリプト
● こんな shell script を作る
#!/bin/bash
sbcl eval "(progn (load "${1}.lisp") (sbext:savelispanddie "./
${1}" :purify t :executable :t))"
sbcc とか名前付けてパーミッションも +x しとく
● foo.lisp をコンパイルして実行可能ファイルを作るとき
$ sbcc foo
● カレントディレクトリに foo という実行可能ファイルが出来る
● ./foo とやれは実行できる
● foo には Lisp 環境が全部詰まってるので超巨大
● ldd v foo とかすればわかるが依存ライブラリはほとんどない
– つまり SBCL がインスコされてないマシンでも実行可能だ。
- 17. Common Lisp のパッケージ
● Python で言うところのモジュールに近いけど…
● 名前空間の分離
– これは機能的にほぼ同じ
– 明示的に export しなくちゃいけない
– export してないシンボルも強制的に参照できる手段もあって…
● いわゆる import とかがメンドクサイ
– ASDF を使うのが今のところ現実的
● ライブラリとかコンポーネントを作りたい時には避けて通れない
- 18. ASDF でできること
● 一定のルールでモジュールを定義する
● asd ファイル
● package.lisp ファイル
● 実際のプログラムが書かれてる lisp ファイル
● ルールに従うことで得ることができるご利益
● 自動コンパイル
● 簡単ロード
● 依存関係解決
gentoo で emerge sbcl してれば asdf は組み込まれた状態になっているので、特
になにか準備するとかの必要は無い。
ちなみに、今まで使ってきた QuickLisp も低レベル部分は asdf を使ってるので、
QuickLisp 経由でインストールしたモジュール asdf で単独ロードできる。
- 19. 超簡単な例
● ライブラリ置き場のディレクトリを適当に作る
● mkdir ~/Temp/mycllib
● 環境変数をセット
● export CL_SOURCE_REGISTRY=~/Temp/mycllib
● 以下の3つのファイルを作成する
● helloworld.asd
● packages.lisp
● helloworld.lisp
- 20. helloworld.lisp
● これがモジュールの本体
●
(inpackage :helloworld)
●
(defun greeting (name)
●
(format t " こんにちわ ~a~%"
(clsql:sql
●
(if (or (null name) (string= name ""))
●
" 名無しさん " name))))
● 先頭の in-package でカレントパッケージを指定しておく
● その他の部分は普通に書けばいい
● 他のモジュールを使いたい場合、明示的にロードする必要は無い
● ASDF が依存関係を解決して自動ロードしてくれる
なんか無理やり clsql に依存させてるけど、気にするな。これは練習だ。
- 21. packages.lisp
● パッケージ名と依存関係を定義する
(inpackage :cluser)
(defpackage helloworld
(:use :cl :clsql)
(:export
:greeting))
● 最初に in-package で :cl-user をカレントにしておく
● :use の部分で依存するパッケージを列挙しておく
– cl パッケージへの依存設定を忘れないように!
● :export の部分で外部に公開する関数とか変数を列挙する
- 22. helloworld.asd
● asdf に対するモジュール属性の定義
●
(defpackage :helloworldsystem (:use :asdf))
(inpackage :helloworldsystem)
●
(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" :dependson ("packages")))
●
:dependson ("clsql"))
● :components
– モジュールを構成するファイル間の依存関係を定義する。コンパイル&ロード
順序に影響するのでちゃんと設定しよう。
● :depends-on
– 依存する外部モジュール名を列挙する
- 23. モジュールのロード
● 3 点セットが出来たらロードしてみる
(asdf:oos 'asdf:loadop 'helloworld)
● うまく行けば下のように使えるようになる
(helloworld:greeting "Foo Bar")
(usepackage :helloworld)
(greeting "Foo Bar")
- 24. ライブラリを作るときのディレクトリ
● 例えば下のようにしておくといいかも
drwxrxrx 5 ransui strategy 120 Feb 1 12:51 ../
drwxrxrx 2 ransui techteam 192 Feb 10 18:41 ASD/
drwxrxrx 2 ransui techteam 176 Feb 1 12:47 xlisbz2/
drwxrxrx 2 ransui techteam 272 Feb 10 19:04 xlisdatetime/
drwxrxrx 2 ransui techteam 272 Feb 10 18:15 xlisdbi/
drwxrxrx 2 ransui strategy 256 Feb 1 13:15 xlisremoralog
$ ls al ASD
lrwxrwxrwx 1 ransui techteam 24 Feb 10 18:24 xlisbz2.asd > ../xlisbz2/xlisbz2.asd
lrwxrwxrwx 1 ransui techteam 34 Feb 10 18:41 xlisdatetime.asd > ../xlisdatetime/xlisdatetime.asd
lrwxrwxrwx 1 ransui techteam 24 Feb 10 18:07 xlisdbi.asd > ../xlisdbi/xlisdbi.asd
lrwxrwxrwx 1 ransui techteam 36 Feb 10 16:50 xlisremoralog.asd > ../xlisremoralog/xlisremoralog.asd
● ASD ディレクトリに各モジュールの asd ファイルへのシンボリッ
クリンクをまとめておく
● こうしておくと CL_SOURCE_REGISTRY 環境変数に ASD ディレ
クトリだけを指定しておけば、全部のモジュールが ASDF でロー
ドしたりできるようになる。
- 25. loop マクロ
黒魔術扱いされることも多いけど
実際のプログラミングで使いこなせば超便利
- 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. 単純繰り返し
● カウンタ変数が要らない場合
(loop :repeat N 〜 )
● (dotimes (x N) 〜 ) とかでもイイけど変数使うし、 declare
ignore するのメンドクサイ時に。
- 28. コレクションに対して繰り返し
● Python のシーケンスに対する繰り返しっぽい
:for 変数 :in リスト [by ステップ関数 ] 〜
:on リスト
:across ベクタ
● hash-table に対しての繰り返し
:for 変数 being the hashkeys :in ハッシュ [using (hashvalue 変数 )] 〜 )
hashvalues hashkey
(loop :for key being the hashkeys in MAP using (hashvalue value)
:collect (cons key value))
- 29. 変数を更新しながら繰り返し
● 単純なカウントループじゃないときに
:for 変数 = 初期化式 [then 更新式 ] 〜
● 複数の変数を更新したい時は
(loop :for x = 1 :then (+ x 1)
●
:for y = 10 :then ( y 1) 〜
- 30. ループ終了条件の指定
● コレクションとかいじってる時によく使う
(loop
:for element in '("hello" "end" "world")
:until (string= key "end")
〜)
(loop
:for line = (readline *standardinput* nil nil)
:then (readline *standardinput* nil nil)
:while line
〜)
- 31. 値を集める
● 色々な集約処理ができる
:collect 式 [into 変数 ]
:append
:nconc
:count
:sum
:maximize
:minimize
collect : 式の評価値をリストにまとめる
append : 式の評価値をリストとみなし、結合する
nconc : 破壊的にリストを結合する
count : 式の評価値が真とみなせるものの数を数える
sum : 式の評価値の和を取る
maximize : 最大値を得る
minimize : 最小値を得る
- 32. 条件分岐
● ループ内で条件分岐できる
:if 条件式 〜
:unless
CLUSER> (loop :for x :from 1 :to 10
:if (evenp x) :collect x)
(2 4 6 8 10)
- 33. ループの中断
● break みたいな機能
(loop :named outer for y :from 0 :below 16
(loop :for x :from 0 :below 16
:do (if (null (dosomething x y))
(returnfrom outer (list x y)))))
● 名前をつけないループの名前は nil
● (return 式 ) とした場合は現在の名前の無いループから抜ける
- 34. ループ内変数
● ループ内で有効なローカル変数を定義する
CLUSER> (loop :with inloopvar = 10
:for x :from 0 :to 9
:collect (/ x inloopvar))
(0 1/10 1/5 3/10 2/5 1/2 3/5 7/10 4/5 9/10)
- 35. ループ内に任意の処理を記述する
● :do を使って書く
● ループをネストするときによく使う
(defun makemazemap (mazeimage)
(let ((result '()))
(loop :for y :from 1 :to 6 :do
(loop :for x :from 1 :to 6 :do
(when (ispassage mazeimage x y)
(let ((position (list x y nil))
(movablepositions '()))
(loop :for (dx dy) :in '((0 1) (1 0) (0 1) (1 0)) :do
(when (ispassage mazeimage (+ x dx) (+ y dy))
(push (list (+ x dx) (+ y dy)) movablepositions)))
(push (list position movablepositions) result)))))
(reverse result)))
- 36. その他のトピック
● マクロ
● Lisp を Lisp たらしめる機能
● 簡単なツールとかの範囲では、知らなくてもあまり困らない。
● なんだが「難しい」とか「超すごい」とか言われるけど、結局の S
式を弄りまわしてるだけ。基本は難しくもなんともない。
● 使いどころが重要。何でもかんでもマクロってのは良くない。
● 入出力系
● 標準入出力的な物以外は、ほとんど拡張機能で処理系依存!
● なので実際に処理系のマニュアル読みながらやるしかない。
● 差異を吸収するライブラリがいくつかあるので、それを使う
● スレッド
● もう、処理系毎に違いすぎ…
● でも使いこなすと Lisp の関数的な性質との相性はイイ。
- 38. お勧めの本とか
● これまでの知識があれば、迷うことなく読める
実際に使えるツールをベースにしているのでかな
りお勧め。最初はこれから始めるのがいい。
Lisp のパワーとマクロについ学べる
2冊目に
マクロを極めたい人へ
- 39. お勧めの本とか
教科書として優秀。実践的な例は少ないけれど
基礎をしっかり学びたい人向け。
Common Lisp に限定していないけれど、
「 Lisp の心」を知るための良書。軽快な語り口だが
奥が深い。
Common Lisp の仕様書
内容的にちょっと古い部分もあるけれど、
本格的に Common Lisp を使うようになったときには
座右の書になるはず。値段が…