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.

One - Common Lispでもワンライナーしたい

363 views

Published on

Common Lispにおいてワンライナーやシェル芸をすることは至難です。そこでワンライナーを支援するライブラリ one (https://github.com/t-sin/one) をつくりました。
このスライドは、oneについてlisp meetup で発表したときのものです。

Published in: Software
  • Be the first to comment

One - Common Lispでもワンライナーしたい

  1. 1. One Common Lispでもワンライナーしたい
  2. 2. 自己紹介 ● twitter: @sin_clav ● github: @t-sin ● 野生のCommon Lisp使い ● おしごとはnil
  3. 3. 今日お話しすること ● Common Lispでワンライナー やったらツラかった(動機) ● ワンライナーを楽にしてやるぜ(結果) ● どうワンライナーするのか(使い方) ● その内部構造(設計・実装)
  4. 4. シェルのワンライナー よくお世話になりますよね
  5. 5. よくやるワンライナー(1) ● アクセスログ中のhoge APIへのアクセス ● そのログ中のhoge APIのアクセス数 $ cat /var/log/nginx/access.log | grep /api/hoge xx.xx.xx.xx - - [21/Oct/…] "GET / HTTP/1.1" …. xx.xx.xx.xx - - [22/Oct/…] "GET / HTTP/1.1" …. xx.xx.xx.xx - - [24/Oct/…] "GET / HTTP/1.1" …. $ cat /var/log/nginx/access.log | grep /api/hoge | wc -l ● 3
  6. 6. よくやるワンライナー(2) ● CSVファイル中の2列目の合計 $ cat data.csv id1,1 id2,2 id3,3 $ cat data.csv | awk -F , '{sum+=$2}END{print sum}' 6
  7. 7. よくやるワンライナー(2) ● CSVファイル中の2列目の合計 shell以外の言語(awk)おぼえないとダメ…? $ cat data.csv id1,1 id2,2 id3,3 $ cat data.csv | awk -F , '{sum+=$2}END{print sum}' 6
  8. 8. Common Lispでやってみる ● CSVファイル中の2列目の合計 ● ぜんぶCommon Lispで。 $ cut -d ',' -f 2 data.csv | ros run -e '(print (loop for line = (read *standard-input* nil :eof) until (eq :eof line) sum line))' -q 6 $ ros -s split-sequence -e '(with-open-file (in "data.csv") (print (loop for l = (read-line in nil :eof) until (eq l :eof) sum (parse-integer (nth 1 (split-sequence:split-sequence #, l))))))' -q ● 6
  9. 9. ながすぎてしぬ われわれはどうすればいいのだ……
  10. 10. ワンライナーっぽさのために ● 入力まわりのタイプ数を減らす – *standard-input* – with-open-fileうんぬん – 行に対してのloop ● 「処理」の合成っぽく書けるとよい
  11. 11. ワンライナーを支援するワン! ● CSVの2列目合計をoneで。 ● ぜんぶoneで。 $ cut -d ',' -f 2 data.csv | ros one '(one:for* - < one:read* +> + 0)' 6 $ ros one '(one:for* #P"data.csv" < one:read-line* $ #/(split-sequence #, _) $ #/(nth 1 _) $ parse- integer +> + 0)' 6
  12. 12. Oneとは 「処理」を 連結して 入力をwrapする ライブラリです!!
  13. 13. Oneの機能概要 ゆるいお気持ちでお聞ききください
  14. 14. Oneの基本構文 ● 前段の入力を、次の記号で受ける CL-USER> (one:for 入力 [記号 パラメータ]*)
  15. 15. Oneの基本構文 ● 前段の入力を、次の記号で受ける CL-USER> (one:for 入力 [記号 パラメータ]*) オブジェクト, stream, pathname パイプ的な記号 $, ?, <, >, +> 1引数関数, シンボル
  16. 16. Oneの基本構文 ● 前段の入力を、次の記号で受ける ● 1引数lmbda式用リーダマクロ CL-USER> (one:for 入力 [記号 パラメータ]*) オブジェクト, stream, pathname パイプ的な記号 $, ?, <, >, +> 1引数関数, シンボル ;; (lambda (input) (search "hoge" input)) と同じ #/(search "hoge" _) 入力
  17. 17. 記号`$`: 処理の合成 ● 前段の入力にパラメータ(処理)を合成 ● 例 CL-USER> (one:for 入力 $ 関数or関数名 ...) CL-USER> (one:for 1 $ print) "1" ; 入力にprintが適用された CL-USER> (one:for 1 $ 1+ $ print) "2" ; 1+の後にprintが適用された
  18. 18. 記号`<`: 入力の上を繰り返し ● 前段の入力の上をloopする(関数で) ● 例 CL-USER> (one:for 入力 < 関数or関数名 ...) CL-USER> (one:for '(1 2 3) < cdr $ princ) 123 ; cdrでloopしたものが出力された CL-USER> (one:for #P"nums.csv" < one:read-line* $ print) "id1,1" "id2,2" ; read-line* (:EOFで終わる) でloopした
  19. 19. 記号`?`: 条件によるフィルタ ● 前段の入力のうち述語でtになるもののみ通す ● 例 CL-USER> (one:for 入力 ? 述語 ...) ● CL-USER> (one:for '(1 2 3) < cdr ? oddp $ print) 1 3 ; 奇数だけが出力された
  20. 20. 記号`>`: 処理結果を溜め込む ● 前段の入力をリストに溜め込んで処理する ● 例 CL-USER> (one:for 入力 > 変換する関数 ...) CL-USER> (one:for 1 > identity $ print) (1) ; 入力がリストになる CL-USER> (one:for '(1 2 3) < cdr > identity $ print) (1 2 3) ; 前段の入力すべてがリストになる CL-USER> (one:for '(1 2 3) < cdr > #/(apply #'+ _) $ print) 6 ; 合計された
  21. 21. 記号`+>`: 処理結果を畳み込む ● 前段の入力をバッファせずに畳み込む ● 例 CL-USER> (one:for 入力 +> 2引数関数 [初期値] ...) CL-USER> (one:for '(1 2 3) < cdr +> (lambda (a b) (+ a b)) 0 $ print) 6 ; 足し込まれた ; 長い。lambdaの部分が+だけで書けるといいかも
  22. 22. 実際の使用例 ここからが本当にやりたかったことです
  23. 23. ワンライナーを支援するワン! ● CSVの2列目合計 ● 標準入力をソート $ seq 1 5 | shuf | ros one "(one:for* - < one:read- line* > #/(sort _ #'string<))" 1 … 5 $ ros one '(one:for* #P"data.csv" < one:read-line* $ #/(split-sequence #, _) $ #/(nth 1 _) $ parse- integer +> + 0)' 6
  24. 24. シェル芸(1): 響け!ユーフォニアム 『【ファン迷惑】「響け!ユーフォニアム」という文字列だけで遊 ぶシェル芸人達』, https://togetter.com/li/1041621 $ echo 響け!ユーフォニアム | ros one '(one:for - < one:read-line* $ #/(cons _ (length _)) $ #/(cons (repeat-sequence (car _) (1+ (cdr _))) (cdr _)) $ #/ (batches (car _) (1+ (cdr _))) < cdr $ #/(format t "~a~ %" _))' 響け!ユーフォニアム響 け!ユーフォニアム響け !ユーフォニアム響け! ユーフォニアム響け!ユ ーフォニアム響け!ユー フォニアム響け!ユーフ ォニアム響け!ユーフォ ニアム響け!ユーフォニ アム響け!ユーフォニア ム響け!ユーフォニアム
  25. 25. シェル芸(2): サンシャイン池崎ゲーム https://twitter.com/ziuziu/status/918070729341587457 $ echo いまはもううごかないおじいさんのとけい | ros one '(one:for* - < one:read-line* $ #/(ppcre:regex-replace- all "い" _ "イェー!"))' イェー!まはもううごかなイェー!おじイェー!さんのとけ イェー!
  26. 26. Oneの中身は? (ちなみにここまででスライド26枚です)
  27. 27. 設計のポイント ● 部品となる「処理」を独立させる ● 処理間を渡るデータはLispオブジェクト ● 省タイプ化 – 入力に対する制御構文(loopやif)を隠す – 入力の煩雑な処理(長い名前等)を隠す ● 省メモリ
  28. 28. パイプを見直す $ cat access.log | grep html | sed 日時 | sort
  29. 29. パイプを見直す $ cat access.log | grep html | sed 日時 | sort grep sedcat sort
  30. 30. パイプを見直す $ cat access.log | grep html | sed 日時 | sort grep sedcat sort grep sedcat sort( )) ) )(((
  31. 31. パイプを見直す $ cat access.log | grep html | sed 日時 | sort grep sedcat sort grep sedcat sort( )) ) )((( (sort (sed (grep (cat "access.log"))))
  32. 32. パイプを見直す $ cat access.log | grep html | sed 日時 | sort grep sedcat sort grep sedcat sort( )) ) )((( (sort (sed (grep (cat "access.log")))) 関数合成だ! 〜パイプからLispへ〜
  33. 33. 省メモリを考える ● 入力を全部メモリに置いてはダメ ● 入力はすぐ次の処理に流す ;; ダメなコード ;; (access.logが10GBあったら…?) (sort (mapcar #'sed (mapcar #'grep (cat "access.log"))) #'string<) (sort (loop :for line in (cat "access.log") :when (grep line) :collect (sed line)) #'string<)
  34. 34. 処理フローの組み立て(1) ● 内部でこんなコールグラフをもつ、いっこの lambda式をつくりたい cat grep grep grep grep sed sed sed sort < ? > : 入力の流れ : 処理のlambda式
  35. 35. 処理フローの組み立て(2) ● lambda式を連ねてフローを作る – こんな関数でパーツを… – 連ねる ;;`$`はだいたいこんな感じ (defun compose (fn2 fn1) (lambda (input) (funcall fn2 (funcall fn1 input))) ;; cat | grep | sed (compose (compose #'grep #'sed) #'cat)
  36. 36. 感想 ● lambda式の入れ子地獄はデバッグつらい ● シェルやREPLで、まあまあ使える ● まだまだ改善の余地ありそう – `+>`に2引数lambda式を書くところなど
  37. 37. Oneをためしに使ってみてね。 `$ ros install t-sin/one`で入るよ!

×