More Related Content Similar to Lisp tutorial for Pythonista : Day 2 Similar to Lisp tutorial for Pythonista : Day 2 (20) Lisp tutorial for Pythonista : Day 21. Lisp tutorial for Pythonista.
Day #2 : The Basics.
Ransui Iso
Strategic Technology Group, X-Listing Co, Ltd.
9. 変数は宣言してから使うのが基本
ルーズな Python とは大違いだよ!
管理大好きスーツ族への強力なアピールポイントだ!
警告ガン無視でも、まあ問題は無いんだが気持ち悪いので宣言しとけ。
11. Common Lisp の名前空間
● 大域は package という単位が基本になっている
● 概念的には Python のモジュールに近い
● Python のモジュールより厳密に定義するのでお手軽じゃない
● 何も指定しない時は CLUSER がデフォルト
● cl パッケージに Common Lisp の基本機能が入っている
● イメージとしては
cluser.py の最初で
from cl import * してある感覚。
● もちろんローカル変数とかもちゃんとあるよ
● 関数引数とか、 let フォームとか色々ある
● レキシカルスコープなので、基本部分は Python 感覚で OK 。
12. パッケージ変数の宣言
● defvar と defparameter
● どちらもパッケージ変数を宣言する
● 宣言するときには初期値を与える
● defvar
● 再初期化されない
● defparameter
● 再初期化される
パッケージ変数という用語は一般的じゃない。 Python 風な名前で呼んで
みてるだけ。ほんとは「スペシャル変数」と言う。実は深い意味もあるん
だけど、今は無視しちゃう。
13. 再初期化される?されない?
● 実験してみりゃ一目瞭然
CLUSER> (defvar *foo* nil)
*FOO*
CLUSER> (defparameter *bar* nil)
*BAR*
CLUSER> (setf *foo* 1)
1
CLUSER> (setf *bar* 2)
2
CLUSER> (defvar *foo* nil)
*FOO*
CLUSER> *foo*
1
CLUSER> (defparameter *bar* nil)
*BAR*
CLUSER> *bar*
NIL
パッケージが再読み込みされた時とかに挙動が変わるよ!
15. ソースをファイルに書いて実行する
● Cx 5 2 で新フレームを開いて
● 新しいフレームで Cx Cf hello.lisp とかし
て新規バッファを開く
● コード書く
(defun hello (name)
(format t "Hello ~a~%" name))
(hello "World")
● Cx Cs でセーブ
● Cc Ck で 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"))
(evalwhen (:compiletoplevel :loadtoplevel :execute)
(main)
(quit))
evalwhen は結構使い方が難しい。コンパイル、ロード、実行のタイミングとかは
Lisp 処理系の動きをイメージしないといけない部分があるからね。
17. コマンドラインから起動する
● 実は処理系依存なのよ
● SBCL の場合は下のようにする
$ sbcl script hello.lisp
Hello World
$
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 で言う所の辞書の使い方
● その他
● コマンドライン引数の扱いとか
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)
25. 辞書っぽくアクセスするようにしてみる
(definecondition keyerror (error)
((text :initarg :text :reader text)))
(defun makedict ()
(makehashtable :test #'equal))
(defun setitem (dict key value)
(setf (gethash key dict) value))
(defun getitem (dict key)
(multiplevaluebind (value haskey) (gethash key dict)
(if (not haskey)
(error 'keyerror :text (format nil "KeyError : ~a" key))
value)))
(defun haskey (dict key)
(multiplevaluebind (value haskey) (gethash key dict)
(declare (ignore value))
haskey))
(defun items (dict)
(let ((result nil))
(maphash #'(lambda (key value)
(setf result (cons (cons key value) result))) dict)
result))
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. ハッシュテーブルの生成部分
● キーの比較関数を指定して生成してる
(makehashtable :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)))
(multiplevaluebind (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 (makehashtable :test #'equal))
(setf (gethash "foo" dict) nil)
● 上のとき、 (gethash "foo" dict) → nil
foo というキーが登録されていないから nil なのか?
foo というキーに対応している値が nil なのか?
–
(defun haskey (dict key)
(multiplevaluebind (value haskey) (gethash key dict)
(declare (ignore value))
haskey))
● 多値関数の戻り値を受け取っても使わない場合は「変数使ってない
が大丈夫か?」とコンパイラが五月蝿いので declare を使って
「大丈夫だ。問題ない。」と伝えておくと吉。
34. if フォーム
● 分岐の基本なんだがイマイチな部分もある
(if ( 条件テスト式 ) (then 節 ) (else 節 ))
● then 節と else 節はブロックじゃない。 1 個の式しか書けない !
– しょうがないので progn とか let とか使ってブロック化する。
● Python で言う所の elif が無い!
– ネストして頑張る。
– cond マクロを使う。
– case マクロを使う。
● Python と違って if は " 文 " じゃない!
– 値を返せるよ!戻り値は then 節とか else 節の式の評価値になる
35. let とローカル変数
● 正確には「レキシカル変数」と言う
● 呼称はまあいい。要するに let の範囲内のローカル変数だ
CLUSER> (let ((x 10) (y 20))
(print x)
(print y)
(let ((x "Hello"))
(print x)
(print y)))
● 実行結果は予想のとおり。
● let と関数定義を組み合わせたりと色々な技がある
● let 以外にもレキシカル変数を取り扱うフォームは色々ある
36. lambda 式
● 関数の実体・無名関数
● 関数を使い捨てしたいときとかに良く使う
● let と組み合わせてクロージャとか作ったりもする
(setf (symbolfunction '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 風。
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 (stringtrim '(#Space #Tab #Newline) s))
(setf s (stringdowncase s))
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 してみると…
CLUSER> (defvar lst '(a b c))
CLUSER> (setf (cdr lst) '(1 2))
CLUSER> lst
● 上の結果はどうなるか?
lst
A B C
1 2
47. setf をリストに使う時の注意
● 新しいセル作らずに無理やりポインタ書換え!
CLUSER> (defvar lst '(a b c))
CLUSER> (setf (cdr lst) '(1 2))
CLUSER> lst
lst
(cdr lst) の位置
を強制的に書換えた!
A B C
これらのオブジェクトはもう参照
できない!いずれ GC の餌食にな
る
1 2
49. 結構簡単よ!
ぱいそん
with open("/tmp/foobar.txt", "r") as input_file:
for line in input_file:
print line
りすぷ
(withopenfile (inputfile "/tmp/foobar.txt" :direction :input)
(loop for line = (readline inputfile)
while line
do (princ (format nil "~a~%" line)) ))
● loop マクロは相変わらず魔法だけど何となく分かるでしょ?
● format 関数は超便利。後日まとめて機能を紹介するですよ
50. コマンドライン引数の取得
● *posixargv* というグローバル変数に入ってる
(defun main()
(dolist (arg *posixargv*)
(princ (format nil "~a~%" arg))))
(evalwhen (:compiletoplevel :loadtoplevel :execute)
(main)
(quit))
$ sbcl script argtest.lisp foo bar baz
● dolist フォームの使い方。上の例で十分に分かるかと。