八発白中

はてなブログに引越しました。

自分のTwitpicの画像・動画をダウンロードできるスクリプトを書きました

Twitpicの公式ブログで、Twitpicが今月終了することが告知されているようです。

追記 (2014/09/19): Twitpicが買収されたので終了しない、ということを公式Twitterアカウントでツイートしています。 詳細は今後公開されると思いますが、終了に向けてのダウンロードの必要はなくなりそうです。
追記 (2014/10/17): Twitpicの買収が失敗したようで、再び終了する告知が出ています。

僕は昔少し使っていた程度ですが、それでもサービスが無くなるのは寂しいですね。ブコメで誰か書いているのを見ましたが、こうしてデータが消えていくと文化が後世に残らないので数十年後困ると思う。

ともかく今月の話なので、昔の写真が上がってるからダウンロードしておかないとなー、と思ってバックアップ用スクリプトを書いてみました。

※ちなみに終了告知には「数日中にデータのエクスポート機能を提供する予定」、と書かれているので待っていれば公式のダウンロード機能が使えそうです。

追記 (2014/09/09):

Twitpic公式で写真/動画のエクスポート機能が追加されました。


設定ページの一番下でエクスポートリクエストができ、準備ができたらダウンロードリンクが表示されるようです。


ダウンロードしてみたら、写真や動画だけでなく投稿時のツイート内容がtweets.txtに保存されています。それに倣って僕のスクリプトも同様の内容を出力するようにしました。


投稿日時情報がファイル名に含まれていないのが残念かな……重要だと思うんだけど。

重要そうな機能

  • 動画にも対応しています
  • 投稿日時がファイル名になっているのでいつ投稿したかがわかります
  • ファイル名の最後5ケタはTwitpicのIDです
    • http://twitpic.com/{ID} にアクセスすると対応するTwitpicのページが見られます。
  • 投稿時のコメントをテキストファイルに出力します

必要なもの

使うにはCommon Lisp処理系とQuicklispが必要です。

Common Lisp処理系

特にこだわりがなければSBCLClozure CLがおすすめです。

Mac OSならHomebrewで簡単にインストールできます。

$ brew install sbcl

Ubuntuならapt-getでインストールできるはずです。

$ sudo apt-get install sbcl

Quicklisp

Quicklispをインストールします。

公式サイトの言うとおりにインストールしてもいいですが、以下のコマンドを叩けば一発でインストールできます。

$ (curl -L http://beta.quicklisp.org/quicklisp.lisp && echo '(progn (quicklisp-quickstart:install) (ql:add-to-init-file))') | sbcl

ダウンロード

以下のリンクを右クリックして「リンク先を保存」します。

実行

以下のコマンドを実行します。twitpic-dl.lispの部分は先ほどダウンロードしたファイルのパスです。

$ sbcl --noinform --load twitpic-dl.lisp

f:id:nitro_idiot:20140906105714p:plain

するとユーザ名を聞かれるので自分の名前を入れてEnterを押します。

f:id:nitro_idiot:20140906105549p:plain

ダウンロードが始まります。ファイル名は投稿日時になっています。途中サーバエラーが出た場合は間隔をおいて自動でリトライします。

Ctrl-Cでいつでも終了できます。途中で終了した場合、再度実行すればダウンロードしていないファイルのみダウンロードを再開します。

何か問題があったら@nitro_idiotにご連絡ください。

ソースコード

Gistに上がっています。ライセンスはPublic Domainなのでご自由にご利用ください。

Twitpic backup script

どこでCommon Lispの質問をしたらいいのか

1週間ほど前、Quicklispの作者のZach Beaneがこんなブログ記事をポストしていて少し話題になりました。

Common Lispの質問をする場所はいくつかある。

一般的な質問の場合、

  • Stack Overflowに「Common-Lisp」タグをつけて明瞭な質問をすれば、何人かの詳しい人が即座に回答してくれる。特にRainer Joswigは数百の質問に質の高い情報を提供しているし、他にも多くの人がすばやく回答をくれる。回答はスコア付けされ、一般的には良い回答はスコアが高く、悪い回答はそれなりになる。
  • reddit/r/lisp/r/learnlispに投稿された質問も同様に多数の回答がつけられる。経験上、redditにはより多くの人がいて回答をくれる。個人的には未熟な質問でも良いと思う。回答はスコア付けされ、悪い回答は通常downvoteされる。
  • FreenodeのIRCチャンネル「#lisp」はリアルタイムのやり取りに向いている。たとえ初心者レベルであっても質問は歓迎されるが、良い本を読んでハマったなどの場合であれば尚良い。あなたがもし退屈していて他のIRCの友達と話したいと思っていたとしても、議題は大事にされる。話題に任せて進行して、ときどきCommon Lisp以外の話題は小言を言われる。スパマーや荒らしはkickされてbanされる。回答の質によっては評価されない。5人が一度に各々のビジョンを語れば最も熱意のある (もしくはタイプの速い人) が優位になる。ときどき誰の話を聞けばいいのかわからなくなる。とはいえ素早いIRCフィードバックループはその欠点を差し引いても価値がある。
  • MLのpro@common-lisp.netはいくらか高度な議論がCommon Lispの「プロ」たちによって交わされている。このMLについての公式のお知らせは見つけられないが、概要はCommon Lispを専門的な分野で使っている人々向けのMLで、コンスやマクロがどう動くかについて知りたい人向けではない。
  • 使ったことはないけど、「lispforum.com」も活発のようだ。
  • comp.lang.lispにも質問を読んで質問してくれる人々がいくらかいる。質問はまったくスコア付けされない。WJやgavinoなどの荒らしが数年に渡って投稿を続けている。S/N比が酷いこともある。残っている人々は、どうkillfileを管理するかわかっている人か彼らに罰を与えることに熱心な人だけだ。Google Groups上で読んだり投稿してはいけない。整形された投稿を台無しにしてしまうし、記事のフィルタリングの邪魔になる。私はnews.individual.netからアクセスしていてコストはとても低い。他にも無料のサーバがある。私は投稿を読んだりフィルタリングするのにEmacs用のGNUSを使っている。

その他、処理系別のメーリングリストやQuicklispのメーリングリストが紹介されています。


今年の1月に、「Common LispのStack Overflowの質問が少なすぎる」と話題になったけど、ここではStack Overflowが一番に挙げられるんだなぁと思った。一番良質な回答が得られるってことかな?

個人的ノウハウとして、何か質問したいなー、というときは、とりあえずTwitterに投稿する。すると即座に@snmstsという人が回答してくれる。

日本語で質問できる場所

基本的にどこに行っても英語です。日本語でCommon Lispの質問できる場所・されている場所っていうのは無い気がしています。Twitterで困ったツイートして誰かにキャッチされるのを待つとかでしょうか。

reddit/r/lisp_jaで質問するのも良いかもしれないですが、質問を見たことはありません。

Gaucheの質問であればChaton Gaucheが活発のようです。Kawai Shiroさんが直々に回答してくれます。Common Lisp用のChatonも、やや不活発ではありますが利用されています。

Shibuya.lispのMLにも質問を投げていいはずです。気づけば運営の報告メーリングリストになっていて投げづらい雰囲気ではあるけど。

最近はShibuya.lispが月一イベントの「Meet Up」を開催しているので、直接会って聞くのもありっちゃあり。

僕もしょっちゅうバグ報告をもらう。そういえばもらうのは質問じゃなくてだいたいバグ報告だった。

他に何か良い場所があったら教えて下さい。

追記 (2014/09/09): コメントでtitoshiさんに、2chCommon Lispスレッドも情報交換に使われていると教えていただきました。

Shibuya.lisp TT #8 で「Redesigning Common Lisp」という発表をしました

先日8/30(土)、Shibuya.lisp Tech Talk #8 が開催されました。

イベント概要

イベントではTechnical Talkが5つ、Lightning Talkが9つありました。

参加者は全部で57名。よく使うLisp方言のアンケートを取ったら、だいたいCommon LispSchemeClojureが1/3ずつくらいばらけていました印象です。コミュニティ内に多様性があるのは非常に良いですね。

各発表も話題が多様でした。

とても内容の濃い一日だったと思います。

途中@nobkzさんの発表で、「弊社、Lisperを募集しています!」という求人募集もありました。いいですね、こういうの。

発表しました

僕もTechnical Talkで「Redesigning Common Lisp」と題して40分程度の発表を行いました。

新しくLispエイリアン色のプレゼンテーマを作って使ってみました。

内容は僕が今年始めたプロジェクトの一つの「CL21」についてです。

質問や意見も多くいただきました。ありがとうございました。

話さなかったこと: CL21のこれから

時間の関係と少し話がそれてしまうという理由で削ってしまったことについて補足します。

CL21はこれからCommon Lispの再整理を行い、より小さな言語カーネルを目指す予定です。

f:id:nitro_idiot:20140901113406p:plain

Richard GabrielのWorse Is Betterという記事に、言語を4層に分離するという方法が提案されています。

言語は少なくとも4つの層に分割されるべきだ:

  1. 言語カーネル。この層は単純で実装しやすいものとなる。どんなケースにおいても、動的な再定義は再考を加えて、このレベルでのサポートが必要かどうかを判断するべきだ。私は再定義可能なものがカーネル内に必要だとは思わない。
  2. 言語を肉付けする言語学的な層。この層は実装上の困難が若干伴うかもしれない。そしてここには多分、カーネルで実装するには高価すぎるが割愛するには重要すぎる動的な側面が含まれることになる。
  3. ライブラリCommon Lispにあるものの大半はこの層に置かれる。
  4. 環境として提供されるエピ言語的機能

1番目の層に私は、条件式、関数呼び出し、すべての基礎データ構造、マクロ、単値、非常に基礎的なオブジェクト指向のサポートを含める。

2番目の層に私は、多値とより進んだオブジェクト指向サポートを含める。2番目の層は、環境が提供するに任せるにはあまりに重要すぎる、難易度が高いプログラミング上の構造、しかしそれでいて正確な定義を正当化するだけの意味論的な重要性が十分にある、そういうもののためにある。何らかの形の再定義機能はここに置かれるかもしれない。

3番目の層に私は、シーケンス関数、手の込んだ入出力関数、その他1番目の層と2番目の層に単純に実装できなかったものすべてを含める。これらの関数はリンク可能であるべきだ。

4番目の層に私は、環境が提供でき、またそうすべきであるが、標準化されなければならない機能を含める。典型的な例はCLOSのdefmethodだ。CLOSでは、総称関数はメソッドから成っており、各メソッドは特定のクラスに適用可能である。1番目の層には完全な総称関数のための定義式――つまり総称関数およびその全メソッドの定義が一箇所に集まったもの――がある(第1層のコンパイラがそれらをどう見たいかを反映している)。名前を総称関数に結び付ける手段も用意される。しかしながら、システムを発展させていく途上で、クラスはさまざまな場所で定義され、関連した(適用可能な)メソッドがクラスの隣に見えたほうが道理にかなっている。メソッドを作るその仕組みはdefmethodで、defmethod式は他の定義式の中のどこにでも置ける。

この方法には2つのメリットがあります。

  • 何をどこのレイヤーに入れるべきか、または入れないべきかの議論の基準が明確になる
  • CL処理系の実装が楽になる

CL21における議論で、「この機能を追加してよ」というものはかなり多いですが、それがどの層に属するのか、同じレイヤーの他の部品と比較して汎用性は十分にあるか、などより具体的に話し合うことができます。

もう一つのメリットである処理系の実装を簡単にするのは、実行環境が(再び)多様化しつつある昨今で、実行環境が生き残っていくために必要かなと考えています。

たとえばJSCLというJavaScriptで実装されたCL処理系がありますが、JavaScriptという独特の難しさを抱えており、CLの仕様をすべて満たしきれないでいます。この視点での言語の4層分離は必要そうです。

CL21の現状

こういう話を踏まえて、CL21の現在のステータスを話すと、「約束できることは何もありません」。

まだ議論も続いていて、何もFixできる状態でなく、早々の安定化を目指すよりも本来の目的である「Experiment (実験)」することの意義を優先しています。

何か良いアイデアをお持ちの方はGitHub Issuesでの議論に参加してください。

まとめ

Shibuya.lisp Tech Talk楽しかったですね。運営スタッフの方々お疲れ様でした。会場を貸していただいた株式会社mixiの鈴木さん、ありがとうございました。

また、Shibuya.lispは月一のMeetUpイベントも開催しています。

次回は来月下旬に開催予定なので、興味がある方はぜひ参加を検討ください。

プロジェクトのCommon Lispライブラリ管理ツール「qlot」を作りました

想像してください。

Common Lispで開発しているアプリケーションが手元にありますよね。

それを他の環境、たとえば他の開発者のPC、CI環境やWebサーバなどで動かしたいというときに起こる問題はなんですか。

大きな問題は処理系やライブラリのバージョンが違うために、自分の環境では動くのに他の環境に持っていくと動かないということです。

処理系に関してはCIMを使えば固定できますが、Common Lispにはライブラリのバージョンを固定する方法は現状ほとんど無いために問題になります。

こういった、Common Lispプロジェクトの可搬性を上げるツールとして「qlot」を作りました。

Quicklispの何が問題か

Common LispにはQuicklispという偉大なライブラリインスーラ及びライブラリの中央リポジトリがあります。依存ライブラリも含めてインストール・ロードしてくれるので、Common Lispでの開発が非常に楽になりました。

けれど、Quicklispにはいくつか問題があります。

  • 更新が月一しかない
  • 特定のライブラリのみ違うバージョンを使うことができない

更新が月に一回しかないので、GitHubに上がっている最新のClackを使いたいみたいなことができません。

また、Quicklispはライブラリを毎月のアップデートごとに管理しているので、すべてのライブラリを同じ時期のものを使うしか選択肢がありません。つまりlog4clだけ古いバージョンを使うということもできません。

"local-projects/"やASDFの設定をいじればgit cloneしたライブラリを使うことはできますが、git cloneしたことを忘れてて更新があったあとも古いバージョンをロードしてしまうみたいなことないですか。僕はつい最近もnamed-readtablesでやらかしました。

とにかく、Quicklispは使うライブラリを細かく管理するという目的ではまったく使えないのです。

qlotとは

qlotは、Common Lispライブラリをプロジェクトローカルにインストール・ロードすることができるツールです。

(正確にはプロジェクトローカルにQuicklispをインストールします。)

使い方を見せたほうが早いのでとっとと使い方を紹介します。

使い方

qlfileを置く

プロジェクトで使うライブラリをqlfileに記述します。すべてを記述する必要は無く、書かれていないものであれば最新のQuicklispのdistからロードされます。

たとえばGitHubにある最新のClackとdataflyを使いたい場合は以下のようなqlfileを用意します。

git clack https://github.com/fukamachi/clack.git
git datafly https://github.com/fukamachi/datafly.git

詳しいqlfileの書き方はGitHubのREADMEを参照してください。

単純にquicklispをプロジェクトごとに分離したい場合は空のqlfileを用意してください。

ライブラリをインストールする

qlotを通常通りにql:quickloadでロードします。

(ql:quickload :qlot)

それからqlot:installをプロジェクト名と一緒に呼び出します。

(qlot:install <プロジェクト名>)
(qlot:install :myapp)

:myappであれば(qlot:install :myapp)みたいな感じです。

インストールはこれだけです。プロジェクトルートにquicklisp/ディレクトリとqlfile.lockファイルができているのが分かると思います。

qlfile.lockはインストールした内容をスナップショットとして記録したものです。このファイルがあるとqlot:installはlockを優先します。一度こうしてインストールしておけば、他の環境でqlot:installしたときにも全く同じバージョンのライブラリを使うことが保証されます。

quicklisp/VCSから無視されるようにし、qlfileqlfile.lockを追加しておきます。

$ echo quicklisp/ >> .gitignore
$ git add qlfile qlfile.lock
$ git commit -m 'Start using qlot.'

プロジェクトをロードする

プロジェクトをロードするときはql:quickloadの代わりにqlot:quickloadを実行します。

(qlot:quickload :myapp)

これはプロジェクトローカルのQuicklispを使ってロードする関数です。

ライブラリをアップデートする

一度qlot:installしたあとqlfileを更新したり、ライブラリのバージョンを更新したいときなどはqlot:updateを実行します。

(qlot:update :myapp)

これでquicklisp/の内容とqlfile.lockが更新されます。

デプロイするとき

qlfile.lockさえ環境間で共有しておけば、qlot:installを実行するだけで同じQuicklisp環境が再現されます。

(qlot:install :myapp)

まとめ

使う関数はqlot:installqlot:quickloadqlot:updateの3つだけです。簡単でしょ?

今月のQuicklispアップデートで入る予定なので、試したい方は1、2週間くらい待つと使えるようになると思います。

ソースコードは例によってGitHubに上がっています。Starしてね!

余談: Lispイベント

今月は中旬にモントリオールでInternational Lisp Conferenceがあったり、月末にShibuya.lispのTech Talkがあったりでなかなか良い月になりそうですね。

Shibuya.lispテクニカル・トーク #8
日時: 8/30(土) 11:00 〜 18:30
場所: 株式会社ミクシィ

トークの応募は8月16日(土)までなので、何か話したい人は早めに準備して応募すると良いと思います。

僕も、qlotについてではないですが他のプロジェクトについて話すつもりでいます。みんな参加しましょう。

軽量なCommon LispのDBライブラリ「datafly」を作りました

Common Lispのデータベースライブラリというか、O/Rマッパーとしては3ヶ月前に僕が作ったIntegralがあります。

IntegralはCLOSやMOPなどのCommon Lispの魔術を余すこと無く使い、拡張性や高度なマイグレーション機能もあるライブラリとして他の追随を許しません。

ただ、すべてのアプリケーションでO/Rマッパーのような機能が必要なわけではないでしょう。抽象化レイヤーを薄く保って、極力コントローラブルにしたいという要望もあります。

今回紹介する「datafly」はそういった要求を満たす軽量なDBライブラリです。

dataflyの思想

一般的なO/Rマッパーでは、データベースの「テーブル」と、プログラム言語の「クラス定義」が一対一対応しています。この大きな前提のおかげでデータベースを抽象化でき、まるでクラス定義が(半)永続化しているように錯覚させてくれます。

ただし、そこにはトレードオフがあります。

O/Rマッパーはその性質上データベースやSQL発行を表向き見えなくするものなので、コストのかかるSQL発行が行われているときに気づきづらくなります。

その点、dataflyは逆の思想に基づいています。

dataflyでは暗黙のSQLの発行を行いません。マクロを除く黒魔術は使いません。透明性を重視し、アプリケーションごとの最適化を行いやすくコントローラブルな状態に保ちます。

機能

上述の通り、dataflyはO/Rマッパーではありません。たとえば、dataflyは以下のようなことはしません。

dataflyがやるのはこんなことです。

  • DBコネクション管理
  • CL-DBIをラップして扱いやすく
  • 結果を構造体(Structure)にマッピング
  • inflate

CLOSの標準クラスではなく構造体を使うのでいくらか効率も良いはずです。

クイックスタート

構造体(Structure)へのマッピング

dataflyではSQLの発行方法としてretrieve-oneretrieve-allexecuteの3つの関数があります。すべて引数としてSxQLのクエリオブジェクトを受け取ります。

たとえば、SELECT文を投げて、結果を1つ返して欲しいときはretrieve-oneを使います。

(retrieve-one
  (select :*
    (from :user)
    (where (:= :name "nitro_idiot"))))
;=> (:ID 1 :NAME "nitro_idiot" :EMAIL "nitro_idiot@example.com" :REGISTERED-AT "2014-04-14T19:20:13")

返り値はプロパティリストです。

キーワード引数の:asを指定すると、結果を指定した構造体(Structure)として返します。

(defstruct user
  id
  name
  email
  registered-at)

(retrieve-one
  (select :*
    (from :user)
    (where (:= :name "nitro_idiot")))
  :as 'user)
;=> #S(USER :ID 1 :NAME "nitro_idiot" :EMAIL "nitro_idiot@example.com" :REGISTERED-AT "2014-04-14T19:20:13")

(user-name *)
;=> "nitro_idiot"

この例ではテーブル名と構造体の名前が同じですが、同じである必要は全くありません。dataflyはテーブルとクラスが一対一対応ではないからです。

この自由さは、架空のテーブル――たとえばJOINした結果――などを構造体として扱いたいときなんかに便利です。

;; "user_bank"という名前のテーブルは存在しない。
(defstruct user-bank
  user-id
  name
  bank-balance)

(retrieve-one
  (select (:user_id
           :name
           (:as (:sum (:amount))
                :bank_balance))
    (from :user)
    (left-join :bank_transactions :on (:= :user.id :bank_transactions.user_id))
    (where (:= :name "nitro_idiot")))
  :as 'user-bank)
;=> #S(USER-BANK :USER-ID 1 :NAME "nitro_idiot" :BANK-BALANCE 200000)

いずれの例でも、結果として返ってきた構造体オブジェクトにsetfで変更を加えてもO/Rマッパーのようにデータベースに更新処理が行えるわけではありません。あくまでデータベースから構造体への一方向のマッピングだけを行います。

モデル定義としての構造体

少しずつ複雑な例を紹介していきます。

上の例では単なるCommon Lispの構造体を使いました。

これだけで十分な方も多いかもしれませんが、dataflyでは少し変わった構造体を定義する機能もあります。

使い方は簡単です。defstructの代わりにdefmodelというマクロを使います。

(defmodel user
  id
  name
  email
  registered-at)

アノテーションライブラリのcl-annotを使うと@modelと書くこともできます。

(annot:enable-annot-syntax)

@model
(defstruct user
  id
  name
  email
  registered-at)

以下では@modelを使うものとします。

inflate

@modelをつけると構造体定義にいくつかの特殊なオプションをつけることができます。

その一つが:inflateです。

@model
(defstruct (user (:inflate registered-at #'datetime-to-timestamp))
  id
  name
  email
  registered-at)

(:inflate <カラム> <関数>)を記述すると、オブジェクトを作るときに指定した<カラム>の値に<関数>を自動適用します。この例ではregistered-atというカラムをLOCAL-TIMEのTIMESTAMPオブジェクトに変換します。

(defvar *user*
  (retrieve-one
    (select :*
      (from :user)
      (where (:= :name "nitro_idiot")))
    :as 'user))

;; Returns a local-time:timestamp.
(user-registered-at *user*)
;=> @2014-04-15T04:20:13.000000+09:00

:inflateは複数つけることもできます。また、<カラム>の部分をリストにして複数のカラムを指定することもできます。

オブジェクトからデータベースにINSERT/UPDATE/DELETE文を発行する機能は無いので、反対の:deflateはありません。

:has-a と :has-many

他に指定できるオプションとして:has-a:has-manyがあります。これらはテーブルのカラムの関係性を定義することで、構造体にアクセサを追加する機能です。

@model
(defstruct (user (:inflate registered-at #'datetime-to-timestamp)
                 (:has-a config (select :* (from :config) (where (:= :user_id id))))
                 (:has-many (tweets tweet)
                  (select :*
                    (from :tweet)
                    (where (:= :user_id id))
                    (order-by (:desc :created_at)))))
  id
  name
  email
  registered-at)

(defstruct config
  id
  user-id
  timezone
  country
  language)

(defstruct tweet
  id
  user-id
  body
  created-at)

この例ではuser-configuser-tweetsというアクセサが自動で定義されます。

(defvar *user*
  (retrieve-one
    (select :*
      (from :user)
      (where (:= :name "nitro_idiot")))
    :as 'user))

(user-config *user*)
;=> #S(CONFIG :ID 4 :USER-ID 1 :TIMEZONE "JST" :COUNTRY "jp" :LANGUAGE "ja")

(user-tweets *user*)
;=> (#S(TWEET :ID 2 :USER-ID 1 :BODY "Is it working?" :CREATED-AT @2014-04-16T11:02:31.000000+09:00)
;    #S(TWEET :ID 1 :USER-ID 1 :BODY "Hi." :CREATED-AT @2014-04-15T18:58:20.000000+09:00))

:has-a:has-manyで定義されたアクセサを呼び出すとSELECT文が発行されることに注意してください。

結果は初回でキャッシュされるので、二度以上呼び出しても何回もクエリが発行されるわけではないので安心してください。キャッシュを消すにはclear-object-cachesが使えます。

おわりに

Integralと違ってブログポスト一つでほとんどの機能が紹介できてしまった。JSON吐くだけのWeb APIサーバとかならこの程度で十分ですね。

今回作ったdataflyはGitHubで公開しています。

また、来週の火曜の夜は渋谷でLisp Meetupがあります。興味がある方はどうぞご参加ください。

Lisp Meet Up presented by Shibuya.lisp #16 : ATND
日時: 4/22(火) 19:30 〜 21:30
場所: 渋谷マークシティ ウエスト13階 セミナールーム

参考