One --- ワンライナーのための入力処理
One
Oneはファイルや標準入力の行単位の入力を簡単に扱えるようにする、Common Lispでのワンライナーを支援するライブラリです。
概要
時は20XX年、ぼくはnums.csvの二列目を合計したくなった……。
$ cat nums.csv name1,1 name2,3 name3,5 $ cut -d ',' -f 2 nums.csv | ccl -Q -b -e '(print (loop for line = (read *standard-input* nil :eof) until (eq :eof line) sum line))' 9
いったいどうしたんだ!? この伸びたラーメンみたいなコードは!?
でもぼくは、深呼吸をして、心を落ち着けて、Common Lispの処理系マネージャ(Roswell, CIM, ...)を導入し、あるライブラリを書くことにした。
その結果——
$ cut -d ',' -f 2 nums.csv | ros -s one -e "(print (reduce #'+ (o:for (line :stdin))))" 9
なんてこったい! あのクソloopにクソstandard-inputが見る影もないぜ!
こうしてぼくはperlやrubyを覚えることもなく、よくわからなくて長いpythonのワンライナーに悩まされることもなく、手に馴染んだCommon Lispで入力を処理できるようになったのでした。
めでたしめでたし。
使い方
Oneはふたつのマクロfor
とforl
によって、ファイルや標準入力を一行ずつ読み込む機能を提供します。Pythonなどで
for line in open('data.dat', 'r'): print line
なんて書けたりしますが、oneはこれを実現します。ふたつのマクロfor
とforl
は、一点を除いてまったく同じ動きをするため(違いはあとで説明します)、以降ではfor
を例にして説明します。
基本的な構文はこうです:
(one:for (var input) body*)
たとえば上で述べたpythonのファイル読み込みは、oneでは次のように書けます:
(one:for (line "data.dat") (print line))
標準入力から読み込みたいときは*standard-input*
ではなくキーワードの:stdin
を指定します:
(one:for (line :stdin) (print line))
読み込んだ値を集約したいときは、body部を空にすると各行のリストが得られます。そのリストに対して所望の集約を施してください。
(one:for (line "nums.dat")) ; => (1 3 5) (apply #'+ (one:for (line "nums.dat"))) ; => 9
for
とforl
の違い
for
とforl
の違いは、各行の読み込みにどの関数を使うかという点です。for
ではcl:read
を、forl
ではcl:read-line
を使っています。for
では読み込んだ行が数値だった場合、文字列ではなく数値に変換されます。forl
では文字列のままです。
この違いは重要です。cl:read
を用いるということは、スペースも改行も一緒くたに扱われるということだからです。つまり、スペースなどの入った入力を処理する場合は、forl
を使うのが安全です。for
を使うのは、明らかに数値しか、しかも目的の値しか入っていない入力を扱うときに限るのがよいでしょう(冒頭のcut
したデータを合計するような)。
$ cat data.txt 1 3 5 2 4 6 $ ros -s one -e '(print (one:for (line "data.txt")))' (1 3 5 2 4 6) $ ros -s one -e '(print (one:forl (line "data.txt")))' ("1 3 5" "2 4 6")
より短かく書くために
Oneのパッケージは、より短かく書くためにo
というニックネームをもっています。なのでone
と入力することすら面倒なときはそのニックネームで楽をしましょう。
Oneのこれから
必要に迫られて、とりあえず入力を楽に扱えるようにしました。でも、たかだか合計をするためだけに
$ ros -s one -e '(print (reduce #'+ (o:for (l "nums.dat"))))'
とするのもバカな話で(合計についてはapply #'+
というスマートな方法がありますが)、reduceに渡せる関数をbody部に指定すると、返り値がrecudeされたものになるという機能があると便利かなあと思いました。こんなイメージです:
$ ros -s one -e '(print (o:for (l "nums.dat") #'+))'
いまはこれくらいしか思いつきませんが、使っていけばいろいろと問題点や改善案もでてくることだと思うので、ちょこちょこ自分でつかっていこうと思っています。
おまけ
oneとは別の話ですが、シェルコマンドをシェルスクリプト並にさくっと叩けるといいんじゃないかなあ、と思いました。
Special thanks to...
このコードうごかねー、への指摘からのコードレビュー・アドバイスは非常に助かりました。
ありがとうございました!
- Asataro.Masaiさん
- κeenさん
職場でお昼休みに書いたせいか、概要のテンションがヤバい。