2012/03/17
直交世界の地図
ライブラリや言語要素は、なるべく独立した、互いに直交な機能を提供すべきだ、 という原理を私は信奉している。(1)学習すべき言語機能やAPIの数を抑えつつ、 (2)それらをシンプルに組み合わせることで「できること」を最大化する、 その最適解だからだ。
(単に(1)を最小化したいならS,Kコンビネータでもチューリングマシンでも いいんだけど、それだと(2)で組み合わせる方法が複雑になる。 必要な機能そのものを実現するAPIをその都度作っていると(2)の組み合わせは 最小化されるけれど(1)が爆発する。)
なんだけど、この直交する「軸」が増えてくると、 ほんの2要素の組み合わせであっても、発見が難しくなりすぎるかもしれない。
最近、Gaucheでコンマ区切りファイルを読んで処理するにはどうする、
という話題を見かけた。Gaucheリファレンスで"CSV"を検索したらすぐに
text.csv
モジュールは見つかる (refj:text.csv)。でも
そこにある読み込みAPIはひとつ、
「CVSファイルの1レコードを読み込む手続きを作る」関数
make-csv-reader
だけしかない。
これが例えばRubyのCSVクラスをだと、色々なAPIが提供されていて、 このページだけ見ればすぐに使えそうだ。
http://doc.ruby-lang.org/ja/1.9.3/class/CSV.html
# ファイルから一行ずつ CSV.foreach("path/to/file.csv") do |row| # use row here... end # ファイルから一度に arr_of_arrs = CSV.read("path/to/file.csv") # 文字列から一行ずつ CSV.parse("CSV,data,String") do |row| # use row here... end # 文字列から一度に arr_of_arrs = CSV.parse("CSV,data,String")
Gaucheでは、make-csv-readerの戻り値を
他の手続きと組み合わせて使うことが想定されている。
(do-ec
を使う場合は(use srfi-42)
しておく)。
;; ファイルから一行ずつ (call-with-input-file "path/to/file.csv" (^p (port-for-each (^[row] #|use row here|#) (cute (make-csv-reader #\,) p)))) ;; あるいは (call-with-input-file "path/to/file.csv" (^p (do-ec (: row p (make-csv-reader #\,)) #|use row here|#))) ;; ファイルから一度に (use util.file) (file->list (make-csv-reader #\,) "path/to/file.csv") ;; 文字列から一行ずつ (call-with-input-string "CSV,data,String" (^p (port-for-each (^[row] #|use row here|#) (cute (make-csv-reader #\,) p)))) ;; あるいは (call-with-input-string "CSV,data,String" (^p (do-ec (: row p (make-csv-reader #\,)) #|use row here|#))) ;; 文字列から一度に (call-with-input-string "CSV,data,String" (cut port->list (make-csv-reader #\,) <>))
まあ、RubyのAPIよりやや冗長であることは否定しないけど、 「ポートから1レコード読み込む」という操作さえ提供しておけば、 後の組み合わせは共通だ。 「ファイルから読むAPI」「文字列から読むAPI」… などを個別に提供した場合、 今後パーズすべきフォーマットが増えていった場合に、 各フォーマットモジュール毎に統一した形でそれらを定義していかないとならない。 とはいえ、良く使う操作なら簡潔に書けるようにしておくっていうのも重要なんで、 バランスの問題ではある。
ただ、今回難しいなと思ったのは、
初心者が「CSVファイルを処理したいな」と思って検索してtext.csv
のページに
たどりついても、さてそこからどうするか、次の一手が見えないだろうな、ってことだ。
各モジュールの独立性を高くしておいたことが、使い方を探す立場からは障害になる。
リファレンスマニュアルに具体例をたくさん書いておくというのは当面の解になるだろうけれど、 直交性の軸が増えると組み合わせの数が爆発するわけで、全ての可能性について 例をあげるという方針はスケールしない。
静的型言語だと、「この型の関数をはめ込めるパターンはどれ?」っていう具合に 検索をかけることができる。何らかのメタなアノテーションをAPIにつけられるように しといて、それを手がかりに探索できるようにするって手はあるかもしれない。
もう一つの問題は、要素群 X = {a, b, c,...} と要素群 Y = {j, k, l,...} から
要素を取ってきて組み合わせる、という使い方がポピュラーな場合に、
使う側は、たとえばbとjを組み合わせるなら(use b)
と(use j)
を
書かないとならないこと。具体例としては、ダイジェストのハッシュを計算して
ハッシュ値の16進文字列を得たい、というとき、ハッシュアルゴリズム
(e.g. (use rfc.md5)
) とダイジェスト汎用のユーティリティ (use util.digest)
が必要になる。
こういう組み合わせが増えてくと、アプリケーションの冒頭にuse
がずらずらと
並ぶことになって、これがどうも気になっている。ダイジェストアルゴリズムのモジュール
それぞれに16進文字列化のAPIをつければuseが減るんだけれど、
モジュール間の直交性がそのぶん減ることになる。なんかうまい方法はないかのう。
Tags: Programming, Gauche
narumij (2012/03/18 09:53:50):
narumij (2012/03/18 10:08:54):
shiro (2012/03/18 11:46:34):
narumij (2012/03/18 15:06:32):
Rui (2012/03/18 17:44:38):
Kei (2012/03/18 20:51:32):
aka (2012/03/19 01:43:26):
shiro (2012/03/19 03:04:32):
ryoakg (2012/04/23 07:39:54):
shiro (2012/04/23 07:56:18):
ryoakg (2012/04/23 08:39:15):
shiro (2012/04/23 08:59:28):
ryoakg (2012/04/23 14:58:17):
shiro (2012/04/23 16:07:29):
ryoakg (2012/04/23 22:00:20):
shiro (2012/04/24 01:05:46):