Contenu connexe Similaire à 括弧への異常な愛情 または私は如何にして心配するのを止めてCommon Lispを愛するようになったか (20) 括弧への異常な愛情 または私は如何にして心配するのを止めてCommon Lispを愛するようになったか2. アジェンダ 1. 自己紹介 2. Lisp に対する懐疑 3. Lisp を始めた経緯 4. Lisp との闘い 5. なぜ Lisp なのか 6. Lisper's Delight 3. 1. 自己紹介 松山 朋洋 アリエル・ネットワーク株式会社 @m 2ym http://cx4a.org/ 好きなエディタ GNU Emacs 作ったもの auto-complete.el, popwin.el, popup.el, mongo.el, rsense, gccsense, xkeyremap, etc 4. 1. 自己紹介 プログラミング言語遍歴 2002 年〜 C 2003 年 Visual Basic 6 2003 年〜 2008 年 C++ 2006 年〜 GNU Emacs 2007 年〜 2010 年 Java 2007 年〜 Emacs Lisp 2009 年〜 Ruby 2010 年〜 OCaml, Haskell, Common Lisp パラダイム 手続き型・オブジェクト指向型に触れた期間が長く、関数型 や Lisp に触れた期間は短い。 6. 2. Lisp に対する懐疑 腑に落ちない啓蒙文書 Beating the Averages (Paul Graham), Revenge of the Nerds (Paul Graham), How to Become a Hacker (Eric Raymond), Let Over Lambda (Doug Hoyte), etc 7. 2. Lisp に対する懐疑 キラーアプリケーションの少なさ それほど”パワフル”な言語なのに、キラーアプリケーションが少ない 8. 2. Lisp に対する懐疑 コミュニティの小ささ それほど”魅力的”な言語なのに、そのコミュニティは小さい 9. 2. Lisp に対する懐疑 標準化の停滞 それほど”人気”な言語なのに、なぜ標準化作業が停滞 ( Common Lisp )したり、遅々として進まない( Scheme )のか 10. 2. Lisp に対する懐疑 Lisp の専売特許 ガベージコレクションやラムダ式、マクロの大部分はもはや Lisp の専売特許ではない 11. 2. Lisp に対する懐疑 Emacs Lisp のひどさ ” 括弧だらけの C” 。これがあの ” Lisp” か? 13. 3. Lisp を始めた経緯 会社で立ちあがった、あるプロジェクト 当初は Ruby on Rails か Pylons の二択で考えていた。 ところが、 Common Lisp はどうかと同僚の深町さんに勧誘され、とんとん拍子で決まってしまった。 これが悪夢の始まりであった。 15. 4. Lisp との闘い パッケージ パッケージの名前空間はグローバル。パッケージはモノシリックに設計するのが一般的。パッケージには以下の問題がある。 a. 名前衝突 b. ニックネーム c. 不本意な intern d. 面倒な export 16. 4. Lisp との闘い a. 名前衝突 安易に use-package すると名前の衝突に弱くなるどころか、不可解なバグに悩まされる可能性がある。とはいっても一々 import するのも面倒。 (defpackage foobar (:use :cl :alexandria)) (defpackage foobar (:use :cl) (:import-from :alexandria ...)) 17. 4. Lisp との闘い b. ニックネーム パッケージにニックネーム(あるいは)を付ける合理的な権限を持つのは、パッケージ作成者のみ。 Haskell, OCaml, Python のように、パッケージに一時的に別名を与える手段がない。 -- Haskell - 可能 import qualified Data.Map as M ;; Common Lisp - 不可能 (defpackage foobar (:use :cl) (:rename :alexandria :alex)) 18. 4. Lisp との闘い b. ニックネーム また、パッケージを一時的に use-package することもで きない。 (* OCaml – 可能 *) let open List in rev (map (fun x -> succ x) [1; 2; 3]) ;; Common Lisp – 不可能 (with-use-package :alexandria (plist-alist (remove-from-plist '(:a 1 :b 2) :a)) 19. 4. Lisp との闘い c. 不本意な intern defpackage 中に不本意な intern が発生す る。 ;; *package* = cl-user と仮定 (defpackage foobar (:use *my-varaible* :my-function)) cl-user パッケージに foobar と *my-variable* 、 keyword パッケージに my-function が intern される。気にしたら負け。 (defpackage #:foobar ; 潔癖症 (:use #:*my-varaible* #:my-function)) 20. 4. Lisp との闘い d. 面倒な export export するのが結構面倒臭い。 ;; いちいち :export に追加 (defpackage foobar (:use :cl) (:export :foo :bar ...)) Clojure の defn/defn- に相当するものを用意するとか、 Go のようにシンボルが大文字で始まる場合にリーダーがそのシンボルを export するなど、手段は色々。 21. 4. Lisp との闘い d. 面倒な export 一つの解決策として cl-annot を開発した。 @ に続く二つのフォームをリストで包んで返す簡単なリーダーマクロ。 CL-USER> '@1+ 2 (1+ 2) CL-USER> '@export (defun foo () ...) (progn (export 'foo) (defun foo () ...)) 22. 4. Lisp との闘い CLOS スロットアクセサの名前をどうするかは実は難しい問題。パッケージシステムに関連する問題でもある。 of 派、 prefix 派、 keyword 派があり、それぞれ一長一短。 23. 4. Lisp との闘い CLOS ;; of 派 (defclass person () ((name :accessor name-of) (age :accessor age-of))) (name-of bob) => “Bob” (age-of bob) => 42 アクセサが総称関数であることのメリットを生かせるが、名前衝突しやすい。 24. 4. Lisp との闘い CLOS ;; prefix 派 (defclass person () ((name :accessor person-name) (age :accessor person-age))) (person-name bob) => “Bob” (person-age bob) => 42 名前衝突の危険性は少ないが、冗長。また、継承したクラスのインスタンスに使う場合に、少し違和感がある。 25. 4. Lisp との闘い CLOS ;; keyword 派 (defclass person () ((name :accessor :name) (age :accessor :age))) (:name bob) => “Bob” (:age bob) => 42 keyword パッケージをグローバル名前空間に見立てるテクニック。使い勝手としてはダックタイピングに近くなるが、慣習として定着しなければ、さまざまな危険性を伴う。 26. 4. Lisp との闘い multiple-value-bind ネストした multiple-value-bind をもっと簡単に書きたい。 (multiple-value-bind (a b) (values 1 2) (multiple-value-bind (c d) (values 3 4) (+ a b c d))) 27. 4. Lisp との闘い multiple-value-bind マクロを自作したが、後に metabang-bind がまさにそれだと知った。 (metabang-bind:bind ((values a b) (values 1 2)) (values c d) (values 3 4))) (+ a b c d)) しかし全く使ってない。 28. 4. Lisp との闘い パターンマッチング metabang-bind や cl-pattern でも可能だが、より高速な ML 風のパターンマッチングが欲しかったため、 cl-pattern (後に cl-adt に統合)を開発した。 (match '(“Bob” 42) ((list “Alice” 30) :alice-30) ((list “Alice” 40) :alice-40) ((list “Bob” 30) :bob-30) ((list “Bob” 42) :bob-42)) しかし全く使ってない。 29. 4. Lisp との闘い loop 痒いところに手が届かない loop 。特に、返り値に対して任意の変換を行えないことに不満を感じ、 cl-loop-plus を開発した。 (defun vectorize (seq &optional (element-type '*)) (coerce seq `(vector ,element-type))) (loop for i from 0 below 100 by 2 collect i transform #'vectorize) 全く使わなかったため、廃棄。パーサー部分は cl-ast にマージ。複雑な loop は iterate で。 30. 4. Lisp との闘い 無名関数 一々、ラムダ式を書くのが面倒。 (find-if (lambda (x) (= (length x) 2)) seq) Clojure のような無名関数シンタックスが欲しい。そこで cl-anonfun を開発した。 (find-if #%(= (length %) 2) seq) 全く使ってない。 31. 4. Lisp との闘い 安易なマクロ (defview index () (markup (:html (:body (:h1 “Hello, World”)))) このコードの本当の意味を理解するには、 defview マクロの正確な定義を知っていなければならない。この手のマクロを安易に書いてしまうことが多々ある。 32. 4. Lisp との闘い 安易なマクロ 図らずも cl-annot が良い解決策を与えた。 @view (defun index () ...) アノテーションは、物事に新しい側面(アスペクト)を追加する。その意味で、マクロとは本質的に別物。 ;; 参考: caveman @url GET “/” (defun index (params) ...) 33. 4. Lisp との闘い キャッシュ 特定の関数をキャッシュ( memoize )する仕組みがなかった。そこで clache を開発した。 load-time-value と cl-annot のおかげで非常にシンプルな設計となった。 ;; 宝石 (defmacro with-cache (key &body body) (once-only (key) (with-gensyms (cache) `(let ((,cache (load-time-value (make-hash-table :test 'equal)))) (or (gethash ,key ,cache) (setf (gethash ,key ,cache) (locally ,@body))))))) 34. 4. Lisp との闘い キャッシュ (defun fact (x) (with-cache x (if (<= x 1) 1 (* x (fact (1- x)))))) @cache (x) (defun fact (x) (if (<= x 1) 1 (* x (fact (1- x))))) 35. 4. Lisp との闘い リストの型指定子 リストといっても、 proper list, improper list, association list, property list があって、 proper list にも heterogeneous proper list と homogeneous proper list がある。それらを全て単に list と呼ぶには無理がある。 (defun f (l) (declare (type list l)) ;; l の詳しい型が読み手にもコンパイラにも伝わらない ...) 36. 4. Lisp との闘い リストの型指定子 そこで trivial-types を開発した。 (declare (type proper-list l)) (declare (type (proper-list fixnum) l)) (declare (type (asscoation-list string fixnum) l)) (declare (type (property-list fixnum) l)) 積極的に利用している。 37. 4. Lisp との闘い Web フレームワーク UnCommon Web ドキュメント皆無、瀕死 Weblocks ドキュメント皆無、瀕死 SymbolicWeb ドキュメント皆無、死亡 38. 4. Lisp との闘い Web フレームワーク どれもこれもイマイチ。結局、 Hunchentoot ( HTTP サーバー)を直接操作することになった。 その裏で、深町さんが Clack&Caveman プロジェクトを始動させる。 ;; 当時のコード (defview index () (markup (:html (:body (:h1 “Hello, World”)))) (defroutes map (GET “/” index)) 39. 4. Lisp との闘い データベース 多数のライブラリが存在する。理想は AllegroCache のようなオブジェクトキャッシュデータベース。 オープンソースで現実的な選択肢は、 Elephant, CLSQL, Postmodern の三つ。結局、 CLSQL を選択。 その裏で、 Rucksack のハックと、 CLSQL を使った ORM の開発を始める。最終的には、どちらも頓挫。 40. 4. Lisp との闘い CLSQL の問題点 1 リーダーマクロに強く依存。 (select [name] :from [employee] :where [> [salary] 1000]) ;; 上と同じ (select (sql-expression :attribute 'name) :from (sql-expression :attribute 'employee) :where (sql-operation '> (sql-expression :attribute 'salary) 1000)) 41. 4. Lisp との闘い CLSQL の問題点 1 SBCL+SLIME の環境では、 CLSQL のリーダーマクロを有効にする以下のコードが正しく動作しない。 (clsql:enable-sql-reader-syntax) そもそも、 Common Lisp にはリーダーマクロをうまく管理する手段がなかった。そこで cl-syntax を開発した。 (use-syntax :clsql) ; CLSQL のリーダーマクロを有効にする (use-syntax :cl-interpol) ; CL-INTERPOL の〃 42. 4. Lisp との闘い CLSQL の問題点 2 SQL 生成、データベースアクセス、簡易的な ORM 、キャッシュ機構など、多数のレイヤーがモノシリックに組み込まれている。そのくせ、カラムの追加といった DDL の基本的な操作が定義されていない。 そこで clsql-ddl を開発した。新たな DDL は clsql パッケージに挿入される。 (clsql:rename-table [foo] [bar]) ; FOO を BAR に改名 (clsql:add-column [person] '([age] fixnum)) ; PERSON に AGE を追加 43. 4. Lisp との闘い 要点 1 Common Lisp には、よほどのコストを掛けない限り、どうしようもない問題がいくつも存在する(パッケージシステム、 CLOS 等)。言わば「言語とユーザーとの溝」。それらを改善するには、処理系の実装あるいは仕様の策定を待たなければならない。 44. 4. Lisp との闘い 要点 2 便利系マクロは使わなくなることが多い( cl-loop-plus, metabang-bind, cl-pattern, cl-anonfun )。 恒久的価値を持つその他のマクロとの違い。 ;; 参考: Why I D on't Like EVAL-ALWAYS -- Nikodemus Siivola (defmacro eval-always (&body body) `(eval-when (:compile-toplevel :load-toplevel :execute) ,@body)) 45. 4. Lisp との闘い 要点 3 抽象化が非常にうまくいくこともある( clache )。全ての歯車がぴったり一致し、その上に新たな基盤が作られる。 46. 4. Lisp との闘い 要点 4 マンパワーが分散しやすく、途切れやすい( Web フレームワーク、データベース)。開発者一人一人の好みが多様。皆、カウボーイプログラマー。 MIT/Stanford スタイルの弊害? 48. 5. なぜ Lisp なのか 標準化されているから? 部分的には Yes. HyperSpec, CLtL2 は人類の宝。そこから得る価値は多大。ただ、要点 1 で示したように、古くなった標準は時に足枷となるし、標準だからと言って、必ずしも正しいとは限らない。 49. 5. なぜ Lisp なのか マクロがあるから? 部分的には Yes. 要点 3 で示したように、抽象化が非常にうまくいくケースもある。ただ、要点 2 で示したように、マクロがあるからといって、プログラミングがうまくいくとは限らない。マクロは単なる手段であり、その先にあるもっと本質的なものに注目したほうがよい。 50. 5. なぜ Lisp なのか “ パワフル”だから? No. そんな幻想はとっとと捨てるべき。例えば Common Lisp と Haskell をとってみて、どちらが”パワフル”か言えるだろうか? 51. 5. なぜ Lisp なのか ライブラリがたくさんあるから? No. 結論 4 で示したとおり。 52. 5. なぜ Lisp なのか “ Growing a Language” “ (プログラミング)言語は成長可能でなくてはならない”と主張する Guy Steele の論文および講演動画。 心の中で全ての問題が解決した気がした。 53. 5. なぜ Lisp なのか 本当の理由 Lisp は真の意味で最も”言語”に近いプログラミング言語であるから。 プログラミング言語が成長可能であることに比べれば、括弧の数が多いとか、”パワフル”であるとか、ライブラリがしょぼいとか、その他諸々は些細な問題である。 実は皆、無意識に本当の理由を理解しているのではないか。 54. 6. Lisper's Delight 私の経験 Common Lisp をひとしきり書いたあとで、試しに Emacs Lisp を書いてみると、以前より捗るどころか、出来あがるソースコードが、なんとも美しい。 auto-complete.el と mongo.el を比較。 55. 6. Lisper's Delight 私の経験 汚ないソースコードを綺麗にリファクタリングできたときや、うまい抽象化によって、物事をより直感的に表現できたときの喜び、また、それらを達成できないときの苦痛。 Lisp を書いているとき、人は自由になれる。他のプログラミング言語では、どこか囚われてしまう。 56. 6. Lisper's Delight 手段と目的 手段と目的を、あえて取り違えてみよう。 Lisp でプログラミングすることは目的であり、ソフトウェアを開発することは手段でしかない。 57. 6. Lisper's Delight 気分は小説家 小説家になった気分でプログラミングしてみよう。 Lisp は言語であり、あなたは Lisp で物事を考え、 Lisp で表現することができる。 他人の作品をよんでインスピレーションを得たり、世間が驚く表現方法を発明したりして、互いを切磋琢磨し、より充実した Lisp の文化を築きましょう。