近況

概要

自分の会社もそうだし、どこの会社もそうだと思うけれど、雑談チャット上で「こういう話題があったんだけど」というのが気軽にできると、その話題で盛り上がるし、横の繋がりみたいなのが、なんとなく生まれてきて、雰囲気が良くなる気がするので、そういうのが気軽に出来るといいな、ということを考えてたりしていた。自分の場合、はてなブックマークユーザーというのもあるので、はてなブックマークと同期できればいいなと思ったので、そういうスクリプトを書いた。

で、まあログバーという会社は、Lispを使うことを推奨されている感じなので、せっかくだし、Common Lispで書いてみるかーというのが、今回のきっかけである。

どういう風に作るの

普通のやつらの上を行くために、ちょこちょことCommon Lispで簡単なコードを書いていたりする。基本的にはEmacs上のSlimeでREPLを起動して関数を動かして……」みたいなことをちょこちょこやっていたりしていたけど、だんだん面倒くさくなって、「やっぱCronとかで定期的に実行できるように、スクリプトっぽく起動できないものかなー」みたいな気持ちになったりしてた。

で、最近Shibuya.lispに行った時に、roswellというのがあって「これは凄いなー」と思ったので、手元にあるCommon Lispのコードをスクリプトで動くようにした。ちなみに、roswell自体については、このブログに詳しいので、このブログ記事より参考になる。Linux版はここを参考にインストールするといいと思う。

最近は、Common LispにはQuickLispというものがあり、大抵の便利ライブラリがあるので、俺みたいなクソザコエンジニアでも気軽に書くことが出来る。Beta版ということもあり、GitHubで管理されているのも、ある程度の質を担保していると思う。使い方とかについてはこの辺りの記事を参考にするといいと思う。

実際のソースコード

処理系はClozure CLを使っている。

( let (( *standard-output* ( make-broadcast-stream ))) ( ql:quickload :cl-redis ) ( ql:quickload :drakma ) ( ql:quickload :plump ) ( ql:quickload :clss ) ( ql:quickload :chirp )) ( redis:connect :host "127.0.0.1" ) ( defvar *user-token* ( ccl::getenv "SLACK_TOKEN" )) ( defvar *latest-url-redis* :latest-bookmark-post ) ( defun post-message ( channel text token ) ( drakma:http-request "https://slack.com/api/chat.postMessage" :method :post :parameters `( ( "token" . ,token ) ( "channel" . ,channel ) ( "text" . ,text ) ( "as_user" . "true" ) ) )) ( defun random-> ( text ) ( post-message "#random" text *user-token* )) ( defun hatena-request () ( plump:parse ( drakma:http-request "http://b.hatena.ne.jp/nisemono_san/bookmark" ))) ( defun hatena-bookmark-block () ( clss:select ".entry-block" ( hatena-request ))) ( defun post-bookmark-p ( element ) ( let (( tags ( concatenate ' list ( clss:select "a.user-tag" element )))) ( and tags ( remove-if-not ( lambda ( x ) ( string= "@" x )) ( mapcar #'plump:text tags ))))) ( defun get-bookmark-link ( elem ) ( plump:attribute ( aref ( clss:select ".entry-title a.entry-link" elem ) 0 ) "href" )) ( defun latest-hatena-bookmark () ( let (( bookmark-list ( remove-if-not #'post-bookmark-p ( concatenate ' list ( hatena-bookmark-block ))))) ( when bookmark-list ( get-bookmark-link bookmark-list )))) ( defun hatena->slack () ( let (( hatena-bookmark ( latest-hatena-bookmark ))) ( format t "Get URL: ~A ~%" hatena-bookmark ) ( format t "Previous URL: ~A ~%" ( red:get *latest-url-redis* )) ( when ( string/= ( red:get *latest-url-redis* ) hatena-bookmark ) ( progn ( random-> hatena-bookmark ) ( red:set *latest-url-redis* hatena-bookmark ))))) ( format t "Start.~%" ) ( format t "SLACK TOKEN Settings: ~A ~%" *user-token* ) ( hatena->slack ) ( format t "Done.~%" )

追記

コードレビューを頂きましたので、ここに掲載させて頂きます。

一つの引数に対してconcatenate 'listしているところがあるけど、vectorからlistへの変換は(coerce vec 'list)でできる。mapcarをしたいだけなら変換なしにmapが使える — fukamachi (@nitro_idiot) 2015, 6月 20

ql:quickloadの第一引数はシステム名のリストも受け取ることができるので、複数のquickloadは (ql:quickload '(:drakma :plump)) のようにまとめることができる — fukamachi (@nitro_idiot) 2015, 6月 20

ql:quickloadの出力を抑えたいときは*standard-output*を束縛せずとも、ql:quickloadに:silent tを渡すだけでいい — fukamachi (@nitro_idiot) 2015, 6月 20

概要

簡単な話、自分のブックマークのページを見て、 @ というタグが付いているものの中で最新のものを拾ってくるようにしている。で、Redisを使って、過去に投稿したものかどうかをチェックするという、極めて簡単な作りだ。

ちょっとしたポイント

ポイントとしては、HTMLのパーサーでどれを使おうか、というのでちょっと若干悩んだりした。Common LispのHTMLパーサーというのは様々あって、これかなというのが決めにくいのだけれど、最終的にはplump + clssが直感的で筋がいいかなと思って採用した。

雑感

批判するにしろ、賞賛するにしろ、コードを書いてみないことには始まらないので、書いてみた結果としては、やはりスクリプト言語としてCommon Lispを使うのには、若干の慣れと、処理系による依存性があって、ちょっと違和感があるな、というのが正直なところではある。

確かに、Common LispはLisp系の中ではパワフルだし、素晴らしい言語だとは思うものの、ではそのパワフルさを日常的なバッチ処理でやるというときには、それが足かせになっている印象は否めない。この記事が正しいとするならば、「LispはAIのリサーチを目的として開発されたので、意図的にインタラクティブに作られて」いることが、逆にデメリットになっている感じもある(これはPrologを触った時にも感じたことではある)。もう少し具体的に言うならば、環境変数を取得するさいに、処理系での取得の違いを抽象化する関数が必要になるというのは、あまり健全ではないようには感じる。

追記

このブログを読んだ人が、環境変数の取得に関しては、それ専門のものがあるということがあるということを教えてもらいました。ありがとうございます。

Common Lispの処理系依存がある、という言及はこのスクリプトではccl::getenvだろうか。処理系非依存にするならuiop:getenvかasdf::getenvが使える — fukamachi (@nitro_idiot) 2015, 6月 20

個人的にはRacket LanguageがClojureの次にこなれている言語だと思っているのだけど、日本語の情報が余りにもなさすぎるのがネックではある。

なんかやや批判めいたことを書いてしまったが、同じLisp族とはいえ、ドメインも違うし、得意分野も違うわけで、そういう意味では適所適材だし、Common Lispでなければならない場合もあるとは思う。Common Lispで簡単なスクリプトを書くというのは、ちょっと悩ましいところだな、というのが正直な印象でもある。