Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Lisp tutorial for Pythonista : Day 2

2,996 views

Published on

The Basics

Published in: Technology
  • D0WNL0AD FULL ▶ ▶ ▶ ▶ http://1lite.top/1cjpD ◀ ◀ ◀ ◀
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • 実用言語という側面でやってるので、Lisper的には「ダサイ」コーディングとか「それ違うだろ!」とか色々出てきます。基本的に「普通な感じに使える速いPython」としてLispを見てて、深いところには興味ないので、そこんとこよろしく。
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here

Lisp tutorial for Pythonista : Day 2

  1. 1. Lisp tutorial for Pythonista.Day #2 : The Basics. Ransui Iso Strategic Technology Group, X-Listing Co, Ltd.
  2. 2. 今日はプログラムを書くための 基礎部分を勉強します
  3. 3. 前回の宿題は OK ですか?いちおう Emacs + SLIME が動く前提です
  4. 4. なにはともあれまずは REPL Read, Eval, Print Loop の略語Python なら「インタラクティブシェル」
  5. 5. Emacs 起動して M-x slimeMETA キーの設定無い人は [ESC] 押して [x]
  6. 6. ちゃんと起動するとこうなるはず
  7. 7. ぱいそん>>> 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
  8. 8. なんか警告出てます!; in: LAMBDA NIL;     (SETF X 1); ==>;   (SETQ X 1); ; caught WARNING:;   undefined variable: X
  9. 9. 変数は宣言してから使うのが基本 ルーズな Python とは大違いだよ!管理大好きスーツ族への強力なアピールポイントだ! 警告ガン無視でも、まあ問題は無いんだが気持ち悪いので宣言しとけ。
  10. 10. 変数宣言の前に名前空間の話Python と似てる部分もあるし違う部分もある
  11. 11. Common Lisp の名前空間● 大域は package という単位が基本になっている ● 概念的には Python のモジュールに近い ● Python のモジュールより厳密に定義するのでお手軽じゃない● 何も指定しない時は CL­USER がデフォルト ● cl パッケージに Common Lisp の基本機能が入っている ● イメージとしては cl­user.py の最初で from cl import * してある感覚。● もちろんローカル変数とかもちゃんとあるよ ● 関数引数とか、 let フォームとか色々ある ● レキシカルスコープなので、基本部分は Python 感覚で OK 。
  12. 12. パッケージ変数の宣言● defvar と defparameter ● どちらもパッケージ変数を宣言する ● 宣言するときには初期値を与える● defvar ● 再初期化されない● defparameter ● 再初期化される パッケージ変数という用語は一般的じゃない。 Python 風な名前で呼んで みてるだけ。ほんとは「スペシャル変数」と言う。実は深い意味もあるん だけど、今は無視しちゃう。
  13. 13. 再初期化される?されない?● 実験してみりゃ一目瞭然CL­USER> (defvar *foo* nil)*FOO*CL­USER> (defparameter *bar* nil)*BAR*CL­USER> (setf *foo* 1)1CL­USER> (setf *bar* 2)2CL­USER> (defvar *foo* nil)*FOO*CL­USER> *foo*1CL­USER> (defparameter *bar* nil)*BAR*CL­USER> *bar*NIL パッケージが再読み込みされた時とかに挙動が変わるよ!
  14. 14. CL のパッケージは取扱いが面倒なので、今のところは CL-USER 一辺倒でいく ライブラリとか作るようになったら defpackage とか使うよ
  15. 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. 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. 17. コマンドラインから起動する● 実は処理系依存なのよ ● SBCL の場合は下のようにする$ sbcl ­­script hello.lispHello World$
  18. 18. お待たせしましたプログラム作成のお時間です
  19. 19. すごく教科書的でアレなんですが 「単語数え」 プログラムを書いてみませう
  20. 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. 21. 単語数えプログラムを書くために● ファイル入出力 ● ファイルのオープン ● パスの操作● 文字列操作 ● スペースとかのデリミタでのフィールド分解● 数え上げ ● Python で言う所の辞書の使い方● その他 ● コマンドライン引数の扱いとか
  22. 22. とりあえず辞書から攻め込む 先週の復習からはじめよう
  23. 23. ぱいそんd = dict()d["Hello"] = "World"d["Hello"] → "World"d[1] = 2len(d) → 2for (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. 24. まぁそのままでもイイんだがもちっと親しみやすくしたいよね!
  25. 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. 26. なんか、色々と新しいの出た!細かい部分とか技とかは後日のお楽しみとして ポイントになる部分をザックリと解説
  27. 27. define-condition● Python で言う所の例外に近い ● 厳密には違うけどイメージとしてはこんなかんじclass KeyError(Exception): def __init__(self, text): Exception.__init__(self, text) KeyError は組み込みの例外だが、これはあくまで例だ。気にするな。● Common Lisp では「コンディション」と言う ● 「例外」と呼ばない点に注意 ● 例外的な事象への対応以外にも色々と使える ● Python の try 〜 except よりもずっと強力
  28. 28. eq, eql, equal, equalp● オブジェクトの比較関数だよ ● オブジェクトの同一性と、値としての同値ってのは別腹 ● Python の is と == 演算子の違いに似てる – eq : メモリ上で同一オブジェクトかどうかをチェック – eql : 数値 or 文字の場合は同値か?それ以外は eq する – equal : 値の構造が同一か?内部構造も再帰的にチェック – equalp :equal よりも緩い判定。詳細は CLtL2 参照
  29. 29. ハッシュテーブルの生成部分● キーの比較関数を指定して生成してる (make­hash­table :test #equal) ● #equal という表記は (function equal) の略記法 ● 略記法があるってことは、今後もいっぱいでてくる予感 ● ちなみに比較関数指定を省略したときは #eql がデフォルト● equal を指定している訳 ● 今回は文字列をハッシュのキーにする ● 文字列は (eql "Hello" "Hello") → nil なのだ ● ちなみに純粋な文字列比較関数の string= を使ってもいい – その場合、この hash-table のキーはマジで文字列限定になる。
  30. 30. setf ってなんだ?● これは違和感ないはず(setf x 10)● で、これは?(setf (gethash key dict) value)● そもそも gethash 関数って(gethash "Hello" dict) → "World" ● みたいに値を取り出す関数なんじゃねーの? ● つーことは、 "Hello" とか、出てきた値になんか代入すんの?
  31. 31. setf には値を入れる場所を指定する● ぶっちゃけ、ポインタです ● setf は関数じゃないんです。マクロなんです。つーことは引数は 評価される前の状態で setf に渡されてるということ ● 第一引数は「値を入れる場所」として解釈される dict Hello (gethash "Hello" dict) foo setf マクロと組み合わせてポインタ bar として振る舞う関数には制限がある 詳しくは「 CLtL2 C7.2: 汎変数」を 参照のこと
  32. 32. 多値関数● 複数の値を返す関数 ● Python でタプル返すのとはちょっと違う。def foo(x): return ((True if x % 2 else False), x + 1)(is_odd, next) = foo(3) # OKis_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. 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. 34. if フォーム● 分岐の基本なんだがイマイチな部分もある(if ( 条件テスト式 ) (then 節 ) (else 節 )) ● then 節と else 節はブロックじゃない。 1 個の式しか書けない ! – しょうがないので progn とか let とか使ってブロック化する。 ● Python で言う所の elif が無い! – ネストして頑張る。 – cond マクロを使う。 – case マクロを使う。 ● Python と違って if は " 文 " じゃない! – 値を返せるよ!戻り値は then 節とか else 節の式の評価値になる
  35. 35. let とローカル変数● 正確には「レキシカル変数」と言う ● 呼称はまあいい。要するに let の範囲内のローカル変数だCL­USER> (let ((x 10) (y 20))           (print x)           (print y)           (let ((x "Hello"))             (print x)             (print y))) ● 実行結果は予想のとおり。 ● let と関数定義を組み合わせたりと色々な技がある ● let 以外にもレキシカル変数を取り扱うフォームは色々ある
  36. 36. lambda 式● 関数の実体・無名関数 ● 関数を使い捨てしたいときとかに良く使う ● let と組み合わせてクロージャとか作ったりもする(setf (symbol­function foo) #(lambda (x) (+ x 1))) ||(defun foo(x) (+ x 1))foovaluefunc (function (lambda (x) (+ x 1)))prop Common Lisp のシンボルは値用と関数用の 2 つのスロットを持っ ているのだ!こういうタイプの Lisp を Lisp-2 という。ちなみに Scheme は値と関数のスロットは分かれていない Lisp-1 。 Python も Lisp-1 風。
  37. 37. 文字列いってみよう!
  38. 38. 文字列って実用では重要でも何故か多くの Lisp 本とかでの解説は少なめ
  39. 39. Lisp の文字列● 実体は文字型の 1 次元配列 ● Common Lisp は文字型が存在する。文字型はエンコードとは独立 するように設計されている。が、まぁモゴモゴ… – C の char とは違う。 Java の char に似た立ち位置と思えばいい。 – 文字をリテラルとして書く場合は #A とか #Newline とか書く。 – Python には文字型は無い。 1 文字の文字列として扱うか、エンコードされた整数コード として扱っている ● リテラルで書く場合は "Hello World" のようにダブルクォート で囲んで書く。 Hello のようにシングルクオートはダメ ● Common Lisp の文字列は mutable に振る舞う。取扱い注意。
  40. 40. 文字列が mutable なので● こんなことができちゃいます(defvar s1 "Hello World")(defvar s2 s1)(eq s1 s2)(setf (char s 5) #­)s1s2(eq s1 s2) ● REPL 環境で実験してみて! ● シンボル s1 と s2 は「同一の」文字列を参照している ● setf はやっぱりポインタ操作でしょ? ● メモリ上の文字列を直接書換えてるイメージなわけですよ。
  41. 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. 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. 43. 関数のキーワード引数● Python と同じようにキーワード引数が使えるよ ● 関数の引数部分に &key というマークに続けて書く ● デフォルト値を与える&与えないの選択可ぱいそんdef foo(x, y, z=100): return x + y + zfoo(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. 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. 45. cons をもう少しL1 L2 X Y 1 2 (cons l1 (cons l2 nil)) → ((x y) (1 2)) X Y 1 2
  46. 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. 47. setf をリストに使う時の注意 ● 新しいセル作らずに無理やりポインタ書換え! CL­USER> (defvar lst (a b c)) CL­USER> (setf (cdr lst) (1 2)) CL­USER> lstlst (cdr lst) の位置 を強制的に書換えた! A B C これらのオブジェクトはもう参照 できない!いずれ GC の餌食にな る 1 2
  48. 48. 次はファイルの読み込み
  49. 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. 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. 51. 課題演習「単語数え」プログラム もう作れるよね?

×