Quantcast
Channel: 八発白中
Viewing all 74 articles
Browse latest View live

あなたがLispを無視することができない理由

$
0
0

(この記事はLisp Advent Calendar 1日目のためのエントリです。)

禅が好んで用いる比喩がある。月を指すには指が必要である。だが、その指を月と思う者はわざわいなるかな。
鈴木大拙「禅」

これをLispに例えるなら、こう言うことができるかもしれない。

Lispを書くには括弧が必要である。だが、その括弧をLispと思う者はわざわいなるかな。

Lispを普段書いている身としてはLispについて括弧がどう、というのは些細なものに思えるが、Lispを知らない人からすると自然な考えだろう。人間は知らないものを理解しようとするとき、自分が今まで見たもの、知っているものと比較して手がかりを得ようとする。Lispが他のプログラム言語と比較してユニークなものは、やはりその括弧で表現されたS式だ。

しかし、Lispが括弧を使った奇妙な構文を用いるのは理由がある。そしてそれがLispの強力さを生み、Lispは今まで生き残ってきた。

ここでその強力さを見せられるといいんだけど、それは他のプログラム言語とはあまりに異なるため学ぶのが難しい。しかし、本質的にはシンプルなことだ。その類似性から、しばしばそれは「悟り」と呼ばれている。

Lispの本質は、そのコード自身をLispのデータ構造で表すことができることだ。

これは主に、Lispプログラムを生成するLispプログラムを書くようなときに効果を発揮する。そしてそのようなプログラムをLispでは「マクロ」と呼ぶ。

マクロで何ができるか

Lispを学び、マクロが何であり、何ができるかを学んだ人には2通りの反応が見られる。好むか、恐れるか。

マクロをこの上なく好み、これ無しでプログラムを書くなんて不安でしかない、という人々と、逆に強力すぎるから言語機能としてあるべきではないという人々がいる。

これはLisp界でも見解が分かれるところで、特にSchemeの人々は後者の見方をする人も多いようだ。ただ一致しているのは、それが強力であることだ。あまりに強力なため、誰もそれを無視することはできない。

Lispプログラマにとって、なぜマクロはそんなに重要なのか? 単にマクロが提供するシンタックス上の便利さのためではなく、マクロはプログラムを操作するプログラムであり、そのことは常にLispコミュニティの中心的テーマであり続けているからである。FORTRANが数を、Cが文字とポインタをこき使う言語であるなら、Lispは、プログラムをこき使う言語である。そのデータ構造は、プログラムテキストを表現や操作するために有用である。マクロは、メタ言語で書かれたプログラムの最も直接の例である。Lispはそれ自身のメタ言語であるので、プログラミング言語全体の力が、プログラムテキストを変形する仕事を行なうために用いられ得る。

— Guy Steel Jr. and Richard P. Gabriel 「Lispの進化」

プログラムを生成するプログラムが必要になるのは、言語自身を拡張したいときだ。

たとえば、JavaScriptwith文というものがある。

with (obj) {
    a = b;
}

上のコードは以下のコードのいずれかと等価である。

a = b;
a = obj.b;
obj.a = b;
obj.a = obj.b;

つまり、ブロック内に出てくる変数の名前と同じ名前のobjのフィールドがあるときはobjのフィールドとして扱い、そうでない場合は単なる変数として扱う。

withは明らかにLispの影響を受けたものだが、一般的なJavaScriptコードではあまり使われていないようだ。「JavaScript: The Good Parts」ではこの構文を「悪いパーツ」として使うべきではないと言っている。理由はそれが非常に効率が悪く、変数が何を表しているかを曖昧にするからだ。ただ、その理由は思想が悪いというよりも実装が悪いせいだと思う。

Common Lispにはそれと似たものとしてwith-slotsがある。オブジェクトのスロット名をそのまま変数 (CLではシンボル) に束縛する構文だ。

(defstruct person
  name age)(defvar me (make-person :name"Eitarow Fukamachi" :age 26))(with-slots(name age) me
  (formatt"I'm ~A. ~D years old." name age));-> I'm Eitarow Fukamachi. 26 years old.

Common Lispではwith-slots内で使うオブジェクトのフィールド名を宣言する。宣言したフィールドは有効範囲内でまるで変数のように扱うことができるようになる。

尚この変数はオブジェクトに紐付けられているため、破壊的な変更もすることができる。

(person-age me);=> 26(with-slots(age) me
  (incf age))(person-age me);=> 27

JavaScriptwithがあまり使われない一方、Common Lispwith-slotsが使われる理由は、それが効率が良いからだ。with-slotsが行う変数とオブジェクトの紐付けはコンパイル時にすべて解決される。JavaScriptwithが効率が悪いのは、実行時にならないと変数がオブジェクトのフィールドなのかただの変数なのか解決できないところにある。

with-slotsCommon Lispの言語仕様に含まれるが、もし含まれないとしてもシンボルマクロを使って簡単に実装することができる。一方でPerlにこの構文を導入するのは非常に難しいだろう。

たとえばPerl風に書くと以下のようになるかもしれない。

sub with {
    my ($names, $object, $block) = @_;
    $block->(map {$object->{$_}}@$names );
}

my$person = { name => "Eitarow Fukamachi", age => 26 };

with(['name', 'age'], $person, sub {
    my ($name, $age) = @_;

    printf"I'm %s. %d years old.", $name, $age;
});

ただ、これは破壊的な操作をすることができないという点でJavaScriptwithCommon Lispwith-slotsに劣る。完全な実装をするとしたら、やはりPerl構文解析が必要だ。

好むにしろ嫌うにしろ、誰もLispを無視することができない理由はここにある。通常の言語ならwithを言語仕様に追加するところを、Lispはそれを仕様に追加することなく追加する機能を持つことで回避している。

Perlにできないが、Rubyにはできる、というようなものは言語レベルでは無い。しかし、PerlRubyにはできないが、Lispにはできるというものはある。マクロだ。Rubyはその構文により柔軟なDSLを作ることができる点でいくつかの問題をわかりづらくしてはいるが、それはプログラムの性能を引き換えにしている。Lispではコンパイル時と実行時の分離が簡単に書き分けられるため、性能面での劣化を心配せずに言語を拡張することができる。

言語の代替と進化

with-slotsは言語を少しだけ前進させる一例だったが、JavaScriptにはもう少し大きな視点での言語の進化にも例がある。

JavaScriptがWebアプリケーションで一般的に使われるようになったのも記憶に新しいが、最近では言語としてのJavaScriptではなく、JavaScriptに変換されるような拡張言語が多く出てきて実用化され始めている。

その代表はCoffeeScriptだ。CoffeeScriptはコンパイラとして実装されており、CoffeeScriptプログラムは完全にJavaScriptに変換される。そのコンパイラJavaScriptで実装されている。実行時には変換結果のJavaScriptが使われるため、実行時のオーバーヘッドも無い。

面白いことに、これはまったくLispのマクロを説明したような文章じゃないか。LispのマクロはLispに変換されるLispコンパイラと考えることができる。コンパイラLispで実装されており、実行時のオーバーヘッドは無い。

ただし、JavaScriptにはJavaScriptへのコンパイラを書く機能が言語仕様に含まれないため、CoffeeScriptは別のプログラムとして提供されている点は異なる。

コンパイラを書く機能が言語仕様に含まれるのと含まれないのではどういった違いがあるだろう。言語仕様に含まれないことで蒙るデメリットは何か。

いや、Lispのようにコンパイラを書く機能が言語に含まれているのはプログラム言語としては特異なのだから、むしろLispが持つメリットと言うべきかもしれない。実を言うとLispは、単なるプログラム言語ではない。Paul Grahamがそれについて言及している。

Lispプログラミング言語として設計されたんじゃなかった。少なくとも我々がこんにち使うプログラミング言語と言う意味では。つまりコンピューターに何をすべきかを指示するもの、という意味ではね。McCarthyはその後確かにそういう意味でのプログラミング言語を作ろうとしたけど、こんにちのLispになったものは彼が理論的な実験としてやったもの、チューリングマシンのより便利な代替物を定義しようとした試みの結果なんだ。
Paul Graham「技術野郎の復讐」

問題はCoffeeScriptをさらに拡張する方法が無いことだ。なぜならCoffeeScriptにマクロが無いから。CoffeeScriptよりももっと上位の言語が欲しくなったとき、CoffeeScriptと同じアプローチ、つまりCoffeScriptにコンパイルするプログラムを書く必要がある。

CoffeeScriptを例に出したが、これは他のどのJS代替言語にも言えることだ。その言語が時代に追いつけず進化が止まったとき、その言語は死ぬしかない。

生き残る言語

ある言語でプログラムを書いているとき、その言語がどう生まれてどのように進化し、将来どうなっているかまで想像してプログラムすることは無いかもしれない。しかし、過去を振り返るとそれは誰にとっても無縁ではいられない。

Perl 4は1991年にリリースされたが、1994年には5.0がリリースされた。言語としての互換性は保ってはあるが推奨される構文は変わり、その後Perl 4でプログラムを書く利点はまったくなくなった。Perl 4の寿命は3年しかなかったことになる。

Ruby 1.8は2003年にリリースされたが2007年には互換性の無い1.9がリリースされた。そして2013年の今では非推奨になっている。Ruby 1.8の寿命は長く見たとしても10年だった。

数年後はどうだろう。Ruby 1.9を使っているだろうか。僕は確信が持てない。

これらの原因は、その言語仕様に言語自身が成長する機能が十分についていないからだと僕は思う。そのため機能追加するために仕様が追加されたり、誤った抽象化だったことが判明したときに修正する必要が出てくる。プログラムに仕様変更はよくあることではあるが、プログラム言語がそうころころと仕様を変更していては影響範囲が大きすぎる。

この仮説はLisp方言の一つであるCommon Lispの例にも合致する。Common Lispは1984年に第1版の仕様が策定され、6年後の1990年に第2版が出た。ただしこの改訂はCLOS(オブジェクトシステム)や拡張loopマクロなど、Common Lisp上で既に実装されていたものを仕様に含めただけだった。それからは20年以上改訂されずに今も使われ続けている*1

もちろん、Lispに足りないものもいくつかある。ライブラリは他の言語よりは少なめだし、コミュニティの活発度も比較するとあまり大きいとは言えない。けれど、言語自身に欠陥があるのに比べればまったく些細な問題だと僕は思う。

Lispの括弧はその強力さの結果として見えているだけだ。月を見たいときに重要なのは指ではなく、月であるということを忘れないで欲しい。

実践Common Lisp

実践Common Lisp

Land of Lisp

Land of Lisp

*1:Schemeは言語仕様が定期的に改訂されているが、それはSchemeCommon Lispとは別の思想により設計されていてまだ未完成だからだ。


Sublime Text 2でCommon Lispを書くための環境設定

$
0
0
仕事を成し遂げるのに必要な労力が大きいほど、その仕事をうまく成し遂げられる可能性は低い。
Design Rule Index[第2版]― デザイン、新・25+100の法則

最近、妻にちまちまJavaScriptを教えるということをやっています。

妻はプログラミングができません。プログラムには何ができて何ができないのかや、それぞれの言語の違いもわかりません。ターミナルも使ったことがない。

こういう人にプログラミングを教えようとすると、普段プログラマが何気なくやっていることでも多様な知識が必要なのだな、と思います。

こういった状態の人に最初から多くのことを教えようとすると、学習の労力が増え、肝心の「プログラムができるようになる」という目的を成し遂げられる可能性が低くなります。なので、本質的でないところはまず後回しにしたい。

たとえばプログラムを書くのに必要なものの一つに「エディタ」というものがあります。僕はEmacsを使っているのでEmacsなら使い方を教えられるのですが、Emacsの使い方を妻が一から覚えるのはかなりの苦難になります。C-x C-sでファイル保存かーとか言いながら、配列の添字の最初はゼロだ、とか関連性のない複雑なことを同時に覚えられる人はそう多くないと思います。

そこで妻にはEmacsではなく、Sublime Textを使わせることにしました。

Sublime TextはMacにも似合うおしゃれUIで、最近使っている人もよく見ます。女性にRuby on Railsを教える「Rails Girls」でもSublime Textを推奨しているようです。

使ってもらうならば僕自身もSublime Textをある程度使えないとまずいだろうと思い、自分のMacにもSublime Textをインストールして一通りの開発環境を整えてみたりしました。

そのときついでにCommon Lispの開発環境を整えてみたので、設定方法をまとめておこうと思います。それほど長くありません。EmacsでSLIME環境を整えるよりは簡単な気はします。

Common Lispの開発環境を整える

Sublime Text 2のインストール

以下のページからSublime Text 2をダウンロードします。自分のOSのリンクをクリックしてインストールしてください。

Package Controlをインストール

Package ControlはSublime Textに簡単に拡張をインストール/アンインストールできるようにする拡張です。Lispに限らず必要になるのでインストールしておくことを強くおすすめします。

Sublime Text で Ctrl + `を押すとコンソールが出てくるので、ページ左下のPythonコードをコピペしてEnterキーを押し、実行してください。

必要なパッケージをインストール

Common Lispの開発に必要なパッケージをインストールします。僕は以下の3つをインストールしました。

  • lispindent (必須)
    • Lispコードを適切にインデントするための拡張。
  • SublimeREPL (推奨)
    • Sublime Text上でREPLを実行するための拡張。
  • paredit (任意)
    • 閉じカッコの自動挿入など、Lispでのプログラムを簡単にする拡張。好き嫌いがあるので任意でインストールしてください。

どれもPackage Controlでインストールできます。Preferences > Package Controlで、Install Packageを選択し、出てくる一覧から上の3つをインストールします。

ただし、SublimeREPLは最新のものを使うことを推奨します。理由はCommon Lisp複数処理系の対応が入っているからです (現行版はGNUClispにしか対応していません)。

Preferences > Package Controlで、Add Repositoryを選択し、「 https://github.com/wuub/SublimeREPL」を入力します。これだけで最新のSublimeREPLをインストールできるようになります。

※追記 (2015/01/24): SublimeREPLはほとんどメンテナンスされずに放棄されています。一年前に投げたPull Requestもマージされていません。

Common LispのREPLの挙動に関するいくつかの変更を加えたものが僕のforkにあるので、そちらを使うことを推奨します。

Preferences > Package Controlで、Add Repositoryを選択し、「 https://github.com/fukamachi/SublimeREPL」を入力します。これだけで最新のSublimeREPLをインストールできるようになります。

その後、通常通りInstall PackageでSublimeREPLをインストールしてください。

その他設定

Sublime Textではデフォルトで、開き括弧や引用符の自動補完機能があります。これにより、開き括弧を入力するとカーソルの後ろに閉じ括弧が自動で挿入されます。

これは便利ではあるのですが、一つイケてないのは、シングルクォート (') を入力したときも引用符として対応するシングルクォートがもう一つ入力されてしまうことです。Lispでクォートは引用符ではなく (つまり2つセットではなく) 1つだけで使うものなので、この自動挿入機能は煩わしいだけです。

このシングルクォートの自動補完機能だけを無効にする、ということはできないので、無理やりキーバインドを上書きします。

Preferences > Key Bindings - User

[
  { "keys": ["'"], "command": "insert", "args": {"characters": "'"} }
]

のように記述しておけば、シングルクォートはただの文字として扱われ、自動補完は行われなくなります。

もしくは、Preferences > Settings - More > Syntax Specific - User

{
  "auto_match_enabled": false
}

と書けば、Lispのときだけ自動補完機能が全部オフになります。pareditをインストールした人は自動挿入機能がついているため、こちらの設定のほうがいいかもしれません。

おわりに

Sublime Text上でREPLを動かせ、S式を送ることもできるのでSLIMEっぽい開発スタイルをそのまま実現できます。普通に使っている分にはそれほど悪くないので、十分おすすめできるものでした。

Common Lispを始めてみよう、と思ったとき、一番情報が多いのはEmacsでSLIMEを使うものです。事実、SLIMEほどよく出来た開発環境は無いと思います。けれど、普段からEmacsは使ってない、という人にはそれだけでもハードルが高い。

妻にプログラミングを教えるという経験で、こういうハードルを地道に取り除いていくというのも、なかなか手が回りづらい部分ではあるけれど、コミュニティとして大事なことだよね、と思ったりしました。

Design Rule Index[第2版]― デザイン、新・25+100の法則

Design Rule Index[第2版]― デザイン、新・25+100の法則

Common LispのResqueクローン「Lesque」を作りました

$
0
0

明けましておめでとうございます。今年もよろしくお願いします。

昨年の暮れは、引越しという大掃除を終わらせたこともあってさほど忙しくもなく、とはいえ一年を振り返る気にも到底ならなかったので前々から欲しかったCommon Lispライブラリを作っていました。

ひょっとしたら昨年中に間に合うかもしれない、と思っていたのですが残念ながら間に合わず。今日になってようやくまともに動くまでになったので「Lesque」と名づけてGitHubに公開しました。

Lesqueとは

Lesqueは処理(ジョブ)をバックグラウンドで実行するためのジョブキューイングシステムです。ジョブはRedisにJSON形式で記録され、別プロセスで動いているワーカーが溜まったジョブを捌いていきます。

たとえば、Webアプリケーションでツイート全削除のような時間がかかる処理を同期的にやると、処理が終わるまでユーザを待たせることになります。このツイート全削除の部分だけをジョブとして定義しておき、処理自体はバックグラウンドで実行できればユーザを長く待たせることなくページを表示できます。

PerlではTheSchewartzやQudoのようなMySQLをバックにしたジョブキューが使われていることが多いようですが、RubyPythonではRedisを使ったものがよく使われているようだったのでRedisをバックとしたシステムにしました。

実装はRubyResqueをほぼそのまま移植しています。そのためRedisに記録されたジョブの形式には互換性があります。

LとRの発音の違いにご注意ください。

使い方

まずはLesqueのコネクションを張ります。

(ql:quickload :lesque)(defvar*lesque*(lesque:connect :host"localhost" :port 6379))

次にジョブを追加します。ジョブはただの関数です((クラスにすることもできます。lesque:jobを継承するクラスを作り、lesque:performを実装してください。))。

(defun deferred-job (&rest args);; ここにバックグラウンドで実行する処理を書く)(lesque:enqueue *lesque*"my-queue"'(deferred-job "arg1""arg2"))

これでRedisに記録されました。

別プロセスでワーカーを動かします。lesque-workerは別asdになっていることに気をつけてください。また、ジョブの関数 (この例ではdeferred-job) がこちらのプロセスでも有効である必要があります。

(ql:quickload :lesque-worker)(lesque.worker:run "my-queue")

これでジョブがRedisにenqueueされる度にワーカーが処理してくれます。

失敗したジョブもRedisに記録され、lesque:all-failuresを実行すると失敗したジョブの情報がハッシュのリストで返って来ます。

おわりに

まだ実運用してないのでどんな感じかわかりませんが、まあまったく使い物にならんということは無いと思いますので、機会があればどうぞご利用ください。

高機能なCommon LispのO/Rマッパー「Integral」を作りました

$
0
0

「O/Rマッパー」や「ORM」と聞くだけで顔をしかめる人もいらっしゃいます。たぶん過去にひどい目にあったんでしょうね。その大きな理由の一つがパフォーマンスでしょう。


一昨年のYAPC::Asiaに参加したとき、ORMは使うなという話を4回くらい聞いたのが印象的でした。DBのデータはハッシュで返すか、DBIをそのまま使うほうが良いと。弊社でもパフォーマンス上の問題をわかりづらくしてしまうことから、ORMを使用しないプロジェクトがいくつかあります。

まあ、そりゃDBI使うほうが高速に動くとは思います。

しかし、僕が使っているのは実用的な言語であるCommon Lispです。実行効率と抽象化がとても得意な言語です。さらに優れたオブジェクトシステムであるCLOSも仕様に含まれています。

そこで、既存のO/RマッパーにCommon Lispらしさを加えてみるとどうだろう。

そう思って作ってみたのが、Common Lisp用のO/Rマッパー「Integral」です。

基本的な使い方

IntegralはCLOSのクラスをRDBMSのテーブルと見たててオブジェクトを操作します。

たとえば、ユーザを表すクラスとしてuserクラスがあるとしますね。

(defclassuser()((name :initarg :name)))

Integralではこれに:metaclass:col-typeをつけます。:col-typeにはnameカラムのDBデータ型をシンボルで入れます。ここではとりあえずTEXT型にしておきましょう。

(import'integral:<dao-table-class>)(defclassuser()((name :col-type text
         :initarg :name))(:metaclass <dao-table-class>))

定義はこれだけ。もちろん普通のクラスとして扱えます。make-instanceもできちゃう。

(make-instance'user);=> #<USER %oid: <unbound>>

make-instanceしたオブジェクト一つ一つがレコードを表します。ただし、このままでは記録されません。

保存するためにDBにテーブルを作ります。IntegralはMySQLPostgreSQL、SQLite3で動きますが、今回は最も簡単なSQLite3を使います。

(import'(integral:connect-toplevel integral:ensure-table-exists))(connect-toplevel :sqlite3 :database-name #P"/tmp/test.db");-> To load "dbd-sqlite3":;     Load 1 ASDF system:;       dbd-sqlite3;   ; Loading "dbd-sqlite3";;=> #<DBD.SQLITE3:<DBD-SQLITE3-CONNECTION> #x302002023C6D>(ensure-table-exists 'user);-> CREATE TABLE IF NOT EXISTS "user" ("%oid" SERIAL NOT NULL PRIMARY KEY, "name" TEXT);;=> NIL

これで準備完了。DBに記録するにはsave-daoを呼びます。

(import'integral:save-dao)(let((user(make-instance'user:name"深町英太郎")))(save-dao user));=> #<USER %oid: 1>

ちゃんとINSERTされたか不安なのでSELECTもしてみます。SELECT文はselect-daoで行います。

(import'integral:select-dao)(select-dao 'user);=> (#<USER %oid: 1>)(describe(car*));-> #<USER %oid: 1>;   Class: #<<DAO-TABLE-CLASS> USER>;   Wrapper: #<CCL::CLASS-WRAPPER USER #x3020017FAE0D>;   Instance slots;   INTEGRAL.TABLE::%OID: 1;   NAME: "深町英太郎"

さっきsave-daoしたインスタンスが返って来ました。

条件付きでSELECTしたいときはSxQLと同じくwherelimitなどが使えます。

(import'(integral:where integral:limit))(select-dao 'user(where (:= name "深町英太郎"))(limit 1));=> (#<USER %oid: 1>)

クラス定義の変更をDBスキーマに適用する (マイグレーション)

Integralではクラス定義からテーブル定義ができることは紹介しました。ただ、途中でクラス定義を変更したらどうなるでしょうか。

Integralではクラス定義の変更に追随してDBスキーマを変更する機能があります。「マイグレーション」として広く知られている機能ですね。

たとえばさっきのuserクラスに自己紹介(profile)も欲しいな〜、と思ったら、まずはスロットを追加しましょう。

(defclassuser()((name :col-type text
         :initarg :name)(profile :col-type text
            :initarg :profile))(:metaclass <dao-table-class>))

そして、migrate-tableを実行します。

(import'integral:migrate-table)(migrate-table 'user);-> ALTER TABLE "user" RENAME TO "user8797";;   CREATE TABLE "user" ("%oid" SERIAL NOT NULL PRIMARY KEY, "name" TEXT, "profile" TEXT);;   INSERT INTO "user" ("%oid", "name") SELECT "%oid", "name" FROM "user8797";;=> NIL

マイグレーションで実行されたSQLがログとして出力されて完了しました。

レコードの更新処理

上述のマイグレーションにより、userテーブルにprofileも保存できるようになりました。試しに先ほどのユーザにプロフィールを設定してみます。

(import'integral:find-dao)(defvar*user*(find-dao 'user1));; まだ自己紹介は無い(slot-value*user*'profile);=> NIL(setf(slot-value*user*'profile)"Common Lispとビールが好きです。");=> "Common Lispとビールが好きです。"

スロットにsetfしただけではDBに残らないので、更新を反映してあげます。反映は、新規追加と同じくsave-daoでできます。

(save-dao *user*);=> NIL

これで基本的な使い方は全部です! 簡単でしょ?

秘技: オートマイグレーション!!!

開発時はクラスの再定義をすることが多いですよね。そのたびにmigrate-tableを実行するのはなかなか面倒……。

そんな人のために、Integralにはオートマイグレーション機能が実装されています。

使い方は簡単。integral:*auto-migration-mode*Tに設定してあげれば、クラスが再定義されるたびにマイグレーション処理が走ります。

;; 再定義(defclassuser()((name :col-type text
         :initarg :name)(profile :col-type text
            :initarg :profile)(birthday :col-type date
             :initarg :birthday))(:metaclass <dao-table-class>));-> CREATE TABLE IF NOT EXISTS "user" ("%oid" SERIAL NOT NULL PRIMARY KEY, "name" TEXT, "profile" TEXT, "birthday" DATE);;   ALTER TABLE "user" RENAME TO "user8800";;   CREATE TABLE "user" ("%oid" SERIAL NOT NULL PRIMARY KEY, "name" TEXT, "profile" TEXT, "birthday" DATE);;   INSERT INTO "user" ("%oid", "name", "profile") SELECT "%oid", "name", "profile" FROM "user8800";;=> #<<DAO-TABLE-CLASS> USER>

この機能は僕のお気に入りです。

※ナチュラルにALTER TABLE文が走ってしまうので、本番環境での使用はお控えください。

もう一つの方法――DBスキーマからクラス定義する

CLOSのクラス定義からCREATE TABLE文を発行して開発を行う例を紹介しました。

けれど、「スキーマ定義はCommon Lispではなく直接DB側でやりたいなー」、という方もいらっしゃるでしょう。

Integralではずばりその機能を提供します。

(defclassuser()()(:metaclass <dao-table-class>)(:generate-slots t))

先ほどに比べてスロット定義がなくなり、代わりに:generate-slots tがついています。

あとは普通に使うだけです。

(find-dao 'user1);=> #<USER %oid: 1>

:generate-slotsを使ったときの副次的な作用として、アクセサが勝手に定義されます。

(user-name (find-dao 'user1));=> "深町英太郎"(user-profile (find-dao 'user1));=> "Common Lispとビールが好きです。"

ね、簡単でしょ? まるでActiveRecordみたい。

INSERT、UPDATE、DELETE時のフック

save-daoinsert-daoupdate-daodelete-daoはすべてメソッドになっているので、普通に:before:after:aroundメソッドを定義してやればいくらでもいじれます。CLOS万歳。

inflate と deflate

多くの言語のO/Rマッパーと同様、Integralにも「inflate」と「deflate」という機能が提供されています。これらは、DBからCommon Lispに渡ってくるときのデータの変換、逆にCommon LispのデータからDBへ保存するときのデータの変換を意味しています。

よく使われるのは日付用のカラムの変換ですね。たとえばさっきのuserクラスではbirthdayには文字列が入っています。

(user-birthday (find-dao 'user1));=> "1999-03-10"

文字列じゃなくてlocal-timeのオブジェクトとして返って来て欲しい、っていう場合には以下のようにinflateメソッドを定義します。

(ql:quickload :local-time)(import'(integral:inflate local-time:parse-timestring))(defmethod inflate ((object user)(slot-name (eql'birthday)) value)(and value
       (parse-timestring value)))

もう一度ユーザを取ってくると、ちゃんとlocal-timeのオブジェクトになります。

(slot-value(find-dao 'user1)'birthday);=> @1999-03-10T09:00:00.000000+09:00

local-timeのオブジェクトをそのままDBに記録することはできないので、逆変換のdeflateも定義しないといけません。

(import'(integral:deflate local-time:format-timestring))(defmethod deflate ((object user)(slot-name (eql'birthday)) value)(and value
       (format-timestring nil value)))

これで、DBに記録するときは予め文字列に変換するようになります。

生のSQLが書きたい?

効率や信条のために「SxQLじゃなくて文字列でSQLを書きたいんだよ」という方もいらっしゃるでしょう。そんなときはretrieve-by-sqlを使います。

(retrieve-by-sql "SELECT * FROM user");=> ((:|%oid| 1 :|name| "深町英太郎";     :|profile| "Common Lispとビールが好きです。" :|birthday| "1999-03-10");    (:|%oid| 2 :|name| "深町英太郎";     :|profile| NIL :|birthday| NIL))

:as <クラス名>をつけると結果がplistではなく、そのクラスのインスタンスで返ってきます。

(retrieve-by-sql "SELECT * FROM user" :as 'user);=> (#<USER %oid: 1> #<USER %oid: 2>)

おわりに

長くなるのでとりあえず使い方だけ紹介。MOPによる具体的な実装の話は以下のスーパークールなイベントで話す予定ですので興味があって関東圏に在住の方は是非ご参加ください。見るとあと4枠しかないようです。

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

IntegralはGitHubで公開しています。気に入ってくれたらStarしてくれるとうれしいですよ。

新しいCommon Lisp方言「CL21」を作ったので意見を募集します

$
0
0

昨晩、神の啓示か何か知りませんが、ふと思い立って新しいLisp方言を作りました。

ほとんどの機能はCommon Lisp互換なので「Common Lisp方言」と言うべきかもしれません。

CLerだけでなく、Common Lispをあまり書いたことがない人やそれ以外の言語を使っている方の意見も伺いたいのでぜひ最後までご覧ください。

名前は「Common Lisp in the 21st Century」の略で「CL21」です。

特徴

CL21のチュートリアル

Common Lispと似ている部分が多いので、わかりやすい異なる部分をいくつか紹介します。

Hello, World!

まずはHello, Worldから。

(write-line"Hello, World!");-> Hello, World!;=> "Hello, World!"

普通ですね。

文字列を繋げたい場合はconcatを使います。

(write-line(concat "Hello, ""John McCarthy"));-> Hello, John McCarthy;=> "Hello, John McCarthy"

ハッシュテーブル

次はハッシュテーブル。新しいハッシュの作り方はCommon Lispと同じです。

(defvar*hash*(make-hash-table))

ハッシュから値を取り出すにはgetfを使います。まだ空なのでNILが返ってきます。

(getf*hash*:name);=> NIL

値を代入するにはsetfを使います。

(setf(getf*hash*:name)"Eitarow Fukamachi");=> "Eitarow Fukamachi"(setf(getf*hash* :living)"Japan");=> "Japan"(getf*hash*:name);=> "Eitarow Fukamachi"

ハッシュテーブルを属性リスト (プロパティリスト aka "plist") に変換するにはcoerceが使えます。

(coerce*hash*'plist);=> (:LIVING "Japan" :NAME "Eitarow Fukamachi")

CL21ではgetfcoerceがメソッドとして定義されており、さまざまな型を取ることができるようになっています。

ベクタ

次にベクタ。こちらも作り方はCommon Lispと同じです。

(defvar*vector*(make-array0:adjustablet:fill-pointer0))

長さ可変のベクタに要素を追加するにはpushが使えます。

(push1*vector*);=> 0(push3*vector*);=> 1*vector*;=> #(1 3)

各要素の値にアクセスするにはnthを使います。

(nth1*vector*);=> 3

値をセットするにはsetfを使います。

(setf(nth1*vector*)"Hello, Lispers");=> "Hello, Lispers"*vector*;=> #(1 "Hello, Lispers")

popで最後の値を取り出せます。

(pop*vector*);=> "Hello, Lispers"(pop*vector*);=> 1

ループ

最後の例は繰り返し(ループ)。

Common Lispにはloopという何でもできるミニ言語がありますが、CL21ではもう少し汎用的で一貫性のあるループ構文をいくつか用意していと思っています。

たとえば、whileuntilです。条件式が真や偽である間だけループを繰り返します。

(let((x 0))(while (< x 5)(princ x)(incf x)));-> 01234;=> NIL

もし条件式の返り値をループ内で使いたい場合はwhile-letが使えます。

(let((people '("Eitarow""Tomohiro""Masatoshi")))(while-let (person (pop people))(write-line person)));-> Eitarow;   Tomohiro;   Masatoshi;=> NIL

さらに追加されたループ構文がdoeachです。Common Lispdolistと似ていますが、リストだけでなくすべてのシーケンス (ベクタなど) に使える点が異なります。

(doeach (x '("al""bob""joe"))(write-line x));-> al;   bob;   joe;=> NIL

loop ... collectのようにループ内で値を取り出したい場合はcollectingマクロが使えます。

(collecting
  (doeach (x '("al""bob""joe"))(when(>(length x)2)(collect x))));=> ("bob" "joe")

実装は?

Common Lisp上で実装したため、お使いのSBCL, Clozure CLなどで動くと思います。(一晩で作れたのはそのせい)

インストール

手元で試すには新しくQuicklispのdistをインストールし、ql:quickloadします。

(ql-dist:install-dist "http://qldists.8arrow.org/cl21.txt")(ql:quickload :cl21)

自分のアプリケーションで使う場合はasdファイルの依存ライブラリに:cl21を追加し、以下のようにパッケージを定義します。

(defpackage myapp
  (:use :cl21))(in-package :myapp)

:use :clではなく :use :cl21にするのがポイントです。

デザインポリシー

CL21は以下の3点を念頭に、機能的な意味での「Common Lispのスーパーセット」を目指してデザインしています。

  • 既存のCommon Lispのアプリケーションと完全に問題なく動作する
  • Common Lispが持つ機能は (ほぼ) すべて継承する
  • 速度を意識しない

今存在するCommon Lispのライブラリやアプリケーションと協調して問題なく動くことは最重要です。こうすることで既にあるCommon Lispのライブラリ資産を使うことができます。ClojureJVM上で動くからJava資産が使えるのと一緒ですね。

一番最後の「速度は意識しない」という点はCommon Lisperにとって必ずしも受け入れられるものではないことは知っています。

Common Lispは実用的な言語ですから、Cのように高速なプログラムを書くことができます。高速さがCommon Lispの価値の一つなのに、それを失うことは愚かなことなのかもしれません。

けれど、言語自身の拡張性も僕は同様に大事だと思っています。

たとえば、ハッシュテーブルのようなクラスを作りたいと思ったとき、今のCommon Lispではhash-tableを継承することはできない (built-in-classなので) ので、仕方なくstandard-classを継承したものを作りますが、gethashはできないし、明らかにCommon Lispが持つハッシュテーブルに似せることはできません。

CL21ではgetfが汎用メソッドになっているため、独自クラスにgetfを定義することが可能です。equalequalpも独自のクラスに対して定義することができます。

これらにより、より言語に近いプログラムを書くことができます。

Common Lispとの協調

僕も速度がまったく重要ではないと思っているわけではありません。もし本当に速度が重要な処理であれば、代わりにgethashを使ったり、cl:equalを使ったりして実行時の型チェックをしないようにすればいいだけの話です。

また、僕はいくつかのCommon Lispライブラリを公開していますが、それらをCL21で書きなおす気はありません。今後もCommon Lispライブラリを書くならCommon Lispで書くと思います。

その一方で、知った人間しか使わないようなWebアプリケーションを書くときにはCL21を使います。そのほうが読みやすく簡単にプログラムが書け、プロトタイプを短時間で作れると思うからです。

名前が「21世紀の」とかついてるから無駄に敵を作ってしまった感あるけど、完全に置き換えようとするわけではなく少なくともしばらくは協調していけばいいかなと思っています。

おわりに

ということで何か意見があれば@nitro_idiotまで。ブログ公開前にRedditにも貼られてしまったので、そちらで議論していただいても構いません。

早速「メソッドにしたらRubyみたいな遅い言語になっちまうだろーが」ってコメントがついていて面白いですね。

Lisp Meet Up #13 に参加しました

$
0
0

1/23の夜開催されたイベント、Lisp Meet Up presented by Shibuya.lisp #13 に参加しました。

毎月やっているLisp Meet Upが1周年を迎えたのはめでたいですね。なわたさんと神田さんは表彰されていいと思う。

参加者

最初に自己紹介タイムがありました。使っているLispClojureが一番多かったです。Common Lisp回なのにCommon Lisperは一番少なかったんじゃないかな。

Integralの紹介をしました

先日作ったCommon LispのO/RマッパーのIntegralについて発表しました。30分くらい話したと思います。

既存のCLのO/Rマッパー「Postmodern」と比較し、より開発フローを意識していろいろ良い感じにやってくれるんだよ、という話です。

先日のブログエントリでは書かなかった、どのように実装されているか、という話を中心に話をしようと思っていたので、CLOSとMeta Object Protocolの話も多かったです。Clojure使いの多い会でやるべき話ではなかった……。

後で佐野さんに、「『classをmake-instanceする直前にslotを…』みたいにずっと横文字しゃべってた」って茶化されましたが、Common Lisperには評判が良かったので良しとします。

κeenさんのCIMの発表

前日にタイトルが明らかになったκeenさんの発表は、「Common Lisp Implementation Managerを作りました」という、自分の作ったツールの紹介でした。

CIMの読み方は「ちむ」だそうです。かわいいですね。

このツールは簡単に説明するとCommon Lisp for RVMで、CL処理系とそのバージョンを複数インストールして切り替えることができるもの。タイトル聞いたときから、もうこれが僕が欲しかったものだ!って感じです。

ライブラリを作っていると複数の処理系で試すのが結構面倒で、Jenkinsで自動テストを回すにしてもそれぞれの処理系のコマンドライン引数の差と苦闘しないといけない。SBCLは複数バージョンを同じ環境にインストールするのが面倒で、毎回手動で切り分けをやったりしていました。

この辺りをShellyでいくらか改善しようとしたんだけど、CIMのほうがぴったり需要に合っています。実装もShellyみたいにPerlが必要ではなく、Bourne Shellで書かれているのも良い。

まだバグも多いようですが、αリリース版を試したい方は以下のリポジトリのREADMEに使い方が書いてあります。

Common Lisp処理系について

休憩時間、ayato_pさんに「Common Lisp処理系は何がいいですか」と聞かれました。

隣にいた佐野さんはSBCLを使っておけばいいんじゃないか、と言っていましたが、僕はClozure CLを推しました。

Common Lisp処理系は何がいいか、と聞く人は初心者だから、初心者が困らない処理系をおすすめしなければならない」と思っていて、SBCLはその点ではあまり親切ではありません。エラーメッセージがわかりづらかったり、ASDFでライブラリロードしたときに、Style Warning程度でコンパイルエラーになったりする。そのときはデバッガが立ち上がるから良い、というのはCommon Lispを既に書いている人の意見で、他の言語から来た人はデバッガが立ち上がると混乱するし怖いと思います。そういう理由で、聞かれたら大体Clozure CLをおすすめしています。

SBCLは簡単な型チェックをしてくれるし、何より高速なので、慣れてきたら移るか併用するのがいいかなと思います。

Rubyを追いかける

ayato_pさんがブログで書かれていますが、今回の発表の2つともにRubyの話が出てきました。僕のORMの発表ではActiveRecordが、κeenさんの発表ではRVMが比較として出されました。

言語の表現力や実行速度は確実に優っているのに、その人気度は遥かに引き離されているのは、Common Lispを普及したいと思っている人の悩みの種です。Lispを始めたいと思った人でも、最近はClojureに行ってしまう。今回の参加者のCommon Lisperの数を見ても、Common Lispは全く人気のある言語とは言えない。

ここからはMeet Upと関係無いし完全に私見ですが……もうカッコつけてる場合じゃないと思うんですよ。なりふり構わずRubyClojureを追いかけないといけない。

特にClojureにはヒントがあると考えていて、なぜCommon LispではなくClojureを始める人が多いのか、採用する企業が日本でも増え始めたのかということを真剣に考えないといけない。Clojureを始める人がなぜLispの中でもClojureを選ぶのか。JVMに載ってるからという安心感もあると思いますが、モダンなLispだから、という理由も多くあると僕は推測しています。

先日CL21という方言を紹介しました

CL21の評価は賛否両論でしたが、普段Common Lispを書かない人たちには「良い意味で当たり障りのない言語に見える」などそれなりに評判が良かったのは面白い反応でした。Common Lispも見せ方を変えるだけで反応は変わるのだな、と思いました。

一方でCommon Lisper達には「Rubyみたいな遅い言語になっちまうだろーが」とか「自分はこういうレイヤーは必要ないと思う」などボロクソに言われました。

この反応の差に、Common Lispを普及したいと思っている人は向き合っていかないといけないと思います。

おわりに

話はイベントに戻る……。

今回は2つとも発表がPracticalだったね、と佐野さんが言っていました。良いことです。この調子でもっとLispを盛り上げていきたいですね。

こちらのイベントレポートもご覧ください。

誰向けかわからないCommon Lispでの関数型プログラミング入門とその未来

$
0
0

Lispと言えば関数型言語という印象を持つ人が多いようです。

まあ正直に言うと、Common Lispに関して言えば違うんですけどね。Common Lispは効率のためと言えばループも代入も使いまくるし、構造体もクラスもある。実際書かれたコードも関数型プログラミングとは程遠いことも多くて、たとえば僕が作ったClackのコードを見ればオブジェクト指向言語だって言っても信じると思います。

僕自身、繰り返しをわざわざ再帰で書くよりもloop使うことが多いです。最近loopに頼りすぎてて良くないなーと思うことが多く、Common Lispでも性能が重要でないところは関数型っぽく書く癖をつけないとなー、と思っていろいろ考えています。なんでもloopだと可読性が悪い。

特に、僕が今作っているCommon Lisp方言の「CL21」では関数プログラミングをもっとしやすくする機能を入れたいと思っています。

そういうわけで、最近はCommon Lispでの関数型プログラミングの方法について調べてCL21に取り込むことをしています。

だいたいまとまったので、Common LispとCL21で関数型プログラミングをするチュートリアルみたいなものを午前四時のローテンションで書いてみました。

以下の2章立てです。

今読み返してみると、しれっとScheme知ってる前提だったりして、これ誰向けだよ、みたいな感じですが、まあご容赦ください。というかCL21のほうが本題だったりするので入門っぽい話を読みたくなかったら2章までスクロールしてください。

Common Lispでの関数型プログラミングの現状

関数

Common Lispで関数を定義するにはdefunを使います。

(defun関数名 (パラメータ*)"ドキュメント文字列 (任意)"本体*)

例えば、Schemeで良く使われるiotaは以下のように定義できます。iotastartから始まるn個のリストを返します。

(defun iota (n &optional(start 0))(if(zerop n)nil(cons start (iota (- n 1)(1+ start)))))(iota 10);=> (0 1 2 3 4 5 6 7 8 9)(iota 53);=> (3 4 5 6 7)

無名関数

Common Lispで無名関数を作るにはlambdaを使います。

(lambda(n)(and(zerop n)(integerp n)));=> #<Anonymous Function #x302002ECCE3F>

無名関数を単に呼び出すときは、通常の関数の位置にlambdaフォームを書けばいいだけです。

((lambda(n)(and(zerop n)(integerp n)))3);=> NIL((lambda(n)(and(zerop n)(integerp n)))0);=> T((lambda(n)(and(zerop n)(integerp n)))0.0);=> NIL

高階関数

高階関数とは、関数を引数として受け取ったり、返り値として(無名)関数を返すような関数のことです。

例えば、多くの言語ではmapという関数がありますね。関数とリストを受け取り、リストのそれぞれの要素について関数を適用するようなものです。

Common Lispではこのような機能はmapcarと呼ばれています。

(mapcar#'1+'(1 2 3 4 5));=> (2 3 4 5 6)

#'という記号は他の言語では特殊なので説明が必要ですね。

まずCommon Lispでは関数と変数の名前空間が分かれています (LISP-2という分類)。たとえば、Common Lispには関数listがありますが、同時に同じ名前のlistという変数を定義して使うこともできます。

このとき気をつけなければいけないのは、単にlistと書いたときに、それが変数なのか関数なのかを区別する必要があるということです。Common Lispで通常listを値として評価した場合、変数として扱われます。

(defvarlist'(1 2 3))list;=> (1 2 3)

listを関数として扱いたいときは、#'をつけます。

#'list;=> #<Compiled-function LIST #x3000000B4D6F>

上のmapcarでは1+という変数を渡しているのではなく、関数1+を渡したいので、#'が必要なのです。

また、reduceもよく使われる高階関数です。reduceはリストの先頭から渡された関数に適用し、さらにその返り値とリストの次の値を適用することを繰り返して結果を返す関数です。reduceは他の言語ではfoldとかinjectと呼ばれることもあります。

;; (+ (+ 1 2) 3) と同じ(reduce#'+'(1 2 3));=> 7

高階関数の使い道

上述した高階関数の便利なところは、その汎用性です。引数として渡す関数によってさまざまな用途に使えます。小さく汎用的なパーツを組み合わせて段々と大きくしていく手法はボトムアッププログラミングとして知られていますね。

たとえば、reduceを使うと以下のような関数が簡単に定義できます。

(defun sum (list)"数字のリストを受け取り、その合計値を返す"(reduce#'+list))(defun factorial (n)"数字を一つ受け取り、その階乗を返す。 n! = 1 * 2 * 3 * ... * n"(reduce#'*(iota n 1)))

sumは数字のリストを受け取り、その合計値を返します。factorialは数字を一つ受け取り、その階乗を返します。

mapcarではzipという関数が定義できます。

(defun zip (&rest lists)(apply#'mapcar#'list lists))

applyは一番最後の引数をリストとして扱って関数を呼び出す方法です。

zipは複数のリストを受け取り、その要素一つ一つをまとめあげる関数です。

(zip '(1 2 3)'(a b c)'(松 竹 梅));=> ((1 A 松) (2 B 竹) (3 C 梅))

関数合成 (compose)

さて、高階関数を使って組み合わせることで多くの関数を定義することを説明しました。この章ではさらに「関数合成」というテクニックを紹介します。

たとえば、関数gの返り値を関数fに渡したいとき、(f (g x))のように書けばいいのはわかりますね。

この処理を高階関数に渡すには、無名関数を作るのが最もナイーブな方法です。

(lambda(x)(f (g x)))

しかし、組み合わせる関数がもっと多くなったときはどうでしょうか。

(lambda(x)(f (g (h (i (j (k x)))))))

長くなりますしだんだんと読みづらくなってしまいます。

このようなとき便利なのが「関数合成」です。ここでは関数composeを使います。関数composeCommon Lispの仕様に含まれないため、Alexandriaのようなユーティリティライブラリを使うか、もしくは以下のようにreduceで簡単に定義できます。

(defun compose (fn &rest functions)(reduce(lambda(f g)(lambda(&rest args)(funcall f (apply g args))))
          functions
          :initial-value fn))

composeを使うとさき先ほどの例は以下のようになります。

(compose #'f#'g)

関数を引数で渡すだけなので括弧も少なく済みますね。

例として、リストの各要素の sin(n + 1)をリストとして返す処理は以下のように書けます。

(mapcar(compose #'sin#'1+)'(1 2 3 4 5));=> (0.9092974 0.14112 -0.7568025 -0.9589243 -0.2794155)

mapcarを2回使っても同じ結果が出ますが、リストを2回走査する必要があるし、ループのたびにリストを新しく作るためメモリ消費面でも良いコードではありません。

;; 良くない(mapcar#'sin(mapcar#'1+'(1 2 3 4 5)))

conjoin & disjoin

関数合成の例として、他にもconjoindisjoinという関数もよく使われます。

これらは各関数の返り値の真偽によって関数を複数実行する機能です。

たとえば、「ゼロかつ整数である」という条件の処理は以下のように書けます。

(lambda(n)(and(zerop n)(integerp n)))

この処理もcomposeのときのように、適用する関数が多い場合に煩雑になってしまいます。

このような処理を関数合成で解決するのがconjoinです。

(import'alexandria:conjoin)(funcall(conjoin #'zerop#'integerp)0);=> T(funcall(conjoin #'zerop#'integerp)0.0);=> NIL

conjoinandで関数を繋げた無名関数を返します。一方でdisjoinorで関数を繋げます。

たとえば(disjoin #'plusp #'minusp)はプラスかマイナスの数値なら真を返します。つまりゼロではないという条件になります。

(funcall(disjoin #'plusp#'minusp)100);=> T(funcall(disjoin #'plusp#'minusp)0);=> NIL

ちなみに余談ですが、ゼロかどうかはzeropで判断できるので、その返り値を反転させて返すほうが賢い実装ですね。

(funcall(lambda(n)(not(zerop n)))0);=> NIL

このようなnotを加えるだけの呼び出しもよく使われるので、単に返り値の真偽を反転させる関数を返す関数complementというものもあります。

(funcall(complement#'zerop)0);=> NIL

CL21での関数型プログラミングの未来

さて、ここからが実は本題です。

Common Lisp関数型プログラミングの機能をひと通り紹介しました。Common Lispでも十分に関数型プログラミングができますね。

しかし、僕が作っている新しいCommon Lisp方言の「CL21」では、より関数型プログラミングをしやすいようにするつもりです。たとえば、Common Lispには無いcomposeconjoindisjoincurryrcurryを含めました。

それだけでなく、さらにそれを簡易に使えるリーダマクロもあります。#'です。

Common Lisp#'はシンボルか、lambda式にしか使えなかったのですが、CL21ではこのリーダマクロに機能を追加したもので上書きしています。

実際のコードを見せたほうが早いかもしれません。

(funcall(conjoin #'zerop#'integerp)0);=> T;; ↑と同じ(funcall #'(andzeropintegerp)0);=> T(funcall(disjoin #'plusp#'minusp)0);=> NIL;; ↑と同じ(funcall #'(orpluspminusp)0);=> NIL

#'(and zerop integerp)と書くと conjoinに展開され、#'(or zerop integerp)と書くと disjoinに展開されます。

これは単に短いだけでなく、andorという一般的な単語を使うことで直感的です。

さらに、もう想像つくと思いますが、complementに対応するものはnotです。

(funcall(complement#'zerop)0);=> NIL;; ↑と同じ(funcall #'(notzerop)0);=> NIL

もちろん、これらは組み合わせて使うこともできます。

(remove-if-not #'(andintegerp(or(notevenp)zerop))(iota 11));=> (0 1 3 5 7 9)

composeだけはそのままcomposeを使います。

(mapcar(compose #'sin#'1+)'(1 2 3 4 5));=> (0.9092974 0.14112 -0.7568025 -0.9589243 -0.2794155);; ↑と同じ(mapcar #'(compose sin1+)'(1 2 3 4 5));=> (0.9092974 0.14112 -0.7568025 -0.9589243 -0.2794155)

あまりルールを増やすのは良くないとは思いますが、こういう地味に省略されててかつ見た目もわかりやすいという機能はどんどん取り入れていきたいですね。

ちなみにCL21はGitHubで絶賛開発中で、意見募集や議論はIssuesで行っています。興味があればぜひご参加ください。

まとめ

JavaScriptで学ぶ関数型プログラミング

JavaScriptで学ぶ関数型プログラミング

株式会社はてなを退職しました

$
0
0

二月末日で株式会社はてなを退職しました。二年半の間、大変お世話になりました。

理由。はてなで働き続けて得られる以上のことをしようと思ったから。

この一年くらい、僕は今の自分に何の価値も感じられず、今の自分に何の満足もできていない。それなのに、気を抜いたら現状に甘えて、一年後の自分が想像できる範囲の成長しかできなくなってる。

年末に一年間を振り返るとき「驚くべき進歩だ」と思えなかったら、きっと努力が足りてないんです。そして、一年後の自分が予想できるなら、今歩いている道は間違ってるんだと思う。

そんなことを考えつつ、ちょうど携わったサービスも終了したということもあって、居心地の良いはてなと大好きな京都を離れることにしました。

特に今後について現状で言えることは何もないですし、振り返るほどの立派な功績もないので、よくある退職エントリみたいにかっこいい文章は書けないですが、ご報告として。

こちらからは以上です。


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

$
0
0

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)として返します。

(defstructuser
  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
(defstructuser
  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階 セミナールーム

参考

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

$
0
0

想像してください。

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についてではないですが他のプロジェクトについて話すつもりでいます。みんな参加しましょう。

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

$
0
0

先日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の質問をしたらいいのか

$
0
0

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スレッドも情報交換に使われていると教えていただきました。

追記 (2014/12/17): 先日Stack Overflowの日本語版がパブリックベータリリースされたので、そちらでも質問ができるようになりました。

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

$
0
0

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

Cより高速なCommon Lispコードを書く

$
0
0
Cで書くコードの方がCommon Lispで書くより速いって人がいたら、それは彼のCの技量が高すぎるってことだね。

“If you can't outperform C in CL, you're too good at C.”Eric Naggum

最近、Common Lispの非同期Webサーバ「Wookie」を高速化する過程で、ボトルネックになっていたHTTPリクエストのパース部分を高速に処理するライブラリを書きました。

既存のライブラリ「http-parse」よりも約10倍速く、Cのライブラリ「http-parser」より5%ほど高速です

追記 (2014/10/26):
最適化をやり直し、現在は「http-parse」よりも約27倍速く、Cの「http-parser」より43% (1.4倍) ほど高速です。
追記 (2014/11/05):
設計を見なおして再実装し、現在はCの「http-parser」より6.4倍高速です。

比較対象のCのhttp-parserはNode.jsで使われているものなので、かなりの高速化が施されていると推測されますが、それをCommon Lispに移植したら少しだけ速くなりました。処理系依存のハッキーなテクニックを使ってるわけでもなく標準の機能だけで。

そこで今回は、Common Lispで高速なプログラムを書くときに僕が考えていることを共有しようと思います。ほとんど我流なので間違っているかもしれません。その場合は指摘してください。

処理系はSBCLを前提としています。

プログラムの設計段階で考慮すべきこと

早すぎる最適化は諸悪の根源である。
“Premature optimization is the root of all evil”— Donald Knuth

「早すぎる最適化」という言葉はプログラマの間で有名です。どこがボトルネックかを知ることなくやたらめったらハックをしていくと無駄にコードが複雑になることを諌める言葉です。

とはいえ、このプログラムは高速に動く必要がある、というときには設計段階で考慮しておかなければならないことがいくつかあります。

その一つがデータ構造です。

データ構造を決める

ルール5: データはすべてを決定づける。もし、正しいデータ構造を選び、ものごとをうまく構成すれば、アルゴリズムはほとんどいつも自明のものになるだろう。プログラミングの中心は、アルゴリズムではなくデータ構造にある。

“Rule 5. Data dominates. If you've chosen the right data structures and organized things well, the algorithms will almost always be self­evident. Data structures, not algorithms, are central to programming.”Robert C. Pike

遅いプログラムは、使っているデータ構造が間違っていることが多いです。一番ありがちなのが、何にでもリストを使うというもの。

しかし、Common Lispは豊富なデータ型があり、それぞれの特性を知り、適切にデータ構造を使い分けられることが高速なプログラムを書く近道です。

List vs. Vector

たとえばListとVectorには以下のような特徴があります。

List

  • 長所
    • 可変長のデータを作るときに向いている
    • 要素の型は何でも良い
    • 部分的に切り出して使うのが楽
  • 短所
    • 長いリストの連結などがONの効率性
    • 長さをとる (length) の速度が線形時間かかる
    • indexを指定したランダムアクセスに向かない

Vector

  • 長所
    • simple-vector (非可変長のベクタ) は高速
    • 要素の型がすべて同じときは型宣言をすることで高速になる
    • indexを指定したランダムアクセスに向く
  • 短所
    • 部分的に切り出して使うみたいなときに大変

高速なプログラムを書くには、可能なら長さ固定で要素の型がすべて同じようなVectorを利用することを検討すべきです。

長さを必ずしも固定にしなくても、1024個のバッファを作って数回イテレートするほうが高速という場合もあります。

Hash Table vs. Structure vs. Class

また、似た比較として、ハッシュテーブル、構造体 (Structure)、クラスの使い分けも重要です *1

Hash Table

  • 長所
    • キーに任意のデータが使える
  • 短所
    • アクセスが遅い
    • 継承はできない

Hash Tableを作る関数make-hash-tableはキーワード引数:testでテスト関数を指定することができます。キーにシンボルしか使わない場合は’eq (ポインタによる比較) を指定するとアクセスが高速になります。

Structure

  • 長所
    • 軽量
    • 高速
  • 短所
    • 継承はできない (:includeを使って他の構造体からfieldだけもらうことはできる)

Class

  • 長所
    • 継承ができる
    • つまりユーザに拡張性を残すことができる
  • 短所
    • どのメソッドやアクセサが呼ばれるかが実行時に判定されるので遅い

Common Lispでは、Structureは内部的には配列になっており、かなり高速です。キーが固定のオブジェクトをいっぱい作るようなときはStructureを使うと良いです。

ただし、これはトレードオフでもあります。

もし書いているプログラムが拡張性を持つ必要があるならClassを使ったほうがいいです。ユーザが独自の継承クラスを定義したり、メソッドモディファイアで挙動を変更できる、ということはプログラムの種類によっては重要です。

データのコピーをするな

せっかく良いデータ型を選んでも、データのコピーを行うと遅いしメモリも食います。

とはいえ難しいのは、どこでデータのコピーが行われるのか、というのが自明ではないこと。

たとえば、subseqは必ずシーケンスのコピーを返します。たとえ(subseq seq 0)と呼び出しても、新しいシーケンスを作ります。既存のhttp-parseが遅い理由の一つはこれでした。

シーケンスの部分データを切り貼りして最終的にシーケンスを返したい、ようなプログラムの場合は「XSubseq」を使うと高速です。

他にはリストに対するappendとかreverseとかもありますね。可能ならnconcnreverseを使えると効率的です。

何はともあれベンチマーク

推測するな、計測せよ。
“Don’t guess, measure!”

書き終わったら、何はともあれベンチマークを取ります。

ベンチマーク

Common Lispには標準でtimeというマクロがあり、実行時間やメモリ・CPU使用率などを表示してくれます。

(time(loop repeat 100000do(http-parse parser callbacks data)));-> Evaluation took:;    0.431 seconds of real time;    0.431906 seconds of total run time (0.430379 user, 0.001527 system);    100.23% CPU;    1,288,960,305 processor cycles;    0 bytes consed

これは今後何度も実行することになるので、run-benchmarkなどの名前でどこかに定義しておくのは重要です。修正した内容が高速化に役立ったか、むしろ遅くなったかとかが即座にわかります。

また、この数値がどんどん速くなっていくのを見るのは楽しいです。意外と重要。

プロファイル

プログラム内でどの部分に時間がかかっているかを知るにはプロファイルを取ります。

SBCLでは標準でプロファイラが付属しています。

;; プロファイルを取るパッケージ名を指定(sb-profile:profile “FAST-HTTP” “FAST-HTTP.BYTE-VECTOR” “FAST-HTTP.PARSER” “FAST-HTTP.URL”);; 何かコードを実行(time(loop repeat 100000do(http-parse parser callbacks data)));; プロファイルレポートを表示(sb-profile:report)

fast-httpのプロファイル結果は以下のようになっています。

  seconds  |     gc     |  consed |    calls   |  sec/call  |  name  
----------------------------------------------------------
     0.000 |      0.000 |  32,960 | 43,000,000 |   0.000000 | FAST-HTTP.PARSER::CHECK-HEADER-OVERFLOW
     0.000 |      0.000 |       0 |    200,000 |   0.000000 | FAST-HTTP.PARSER::INIT-MARK
     0.000 |      0.000 |       0 |          1 |   0.000000 | FAST-HTTP.PARSER::MAKE-MARK
     0.000 |      0.000 |       0 |          1 |   0.000000 | FAST-HTTP.PARSER:MAKE-LL-CALLBACKS
     0.000 |      0.000 |  81,712 |    100,000 |   0.000000 | FAST-HTTP.PARSER:HTTP-PARSE
     0.000 |      0.000 |       0 |          1 |   0.000000 | FAST-HTTP.PARSER:MAKE-LL-PARSER
     0.000 |      0.000 | 439,072 |    100,000 |   0.000000 | FAST-HTTP.PARSER:HTTP-PARSE-HEADERS
     0.000 |      0.000 |       0 |    800,000 |   0.000000 | FAST-HTTP.URL:PARSE-URL-CHAR
----------------------------------------------------------
     0.000 |      0.000 | 553,744 | 44,200,003 |            | Total

estimated total profiling overhead: 62.41 seconds
overhead estimation parameters:
  6.e-9s/call, 1.412e-6s total profiling, 5.8999996e-7s internal profiling

どれも合計0秒以下なのでちょっとわかりにくいですが、上から順に時間がかかっている関数が表示されています。GCにかかった時間、メモリの使用量 (consed)、何回呼ばれたか、呼び出し1回あたりの実行時間が並んでいます。

言うまでもなく、対処するべきは上の方にある関数からです。

まずはcallsの欄を見て、その関数が自分の予想を超える回数呼び出されているとしたら、それを減らす方法を考えます。無駄なループなどが見つかるかもしれません。

型宣言とoptimize

ここからはプロファイラの結果に基いて、個別の関数を最適化する方法です。

けど、楽してプログラムが高速になるなら、それほどうまい話はないですよね?

適切なデータ構造を使っている場合、型宣言を追加するだけでかなり高速になることがあります。

特に、「数値」と「配列」に関しては顕著です。

もし変数の数値がfixnumの範囲 *2に収まるなら(declare (type fixnum 変数名))とすると、数値演算や比較の関数がコンパイル時に決定できるので高速になります。

また、配列がsimple-vector (非可変長) の場合、要素の型がすべて同じ場合なども型宣言はかなり有効です。

たとえば (unsigned-byte 8)のsimple-vectorは以下のように宣言します。

;; 長さ不定の非可変長のバイトベクタ(declare(type(make-array(unsigned-byte8)(*))変数名))

長さも固定の場合は以下のように書けます。

;; 長さ3の非可変長のバイトベクタ(declare(type(simple-array(unsigned-byte8)(3))変数名))

型宣言をしたらoptimize宣言も追加します。

(declare(optimise (speed3)(safety2)))

speedは速度優先でコンパイルsafetyは実行時に型チェックを行うかどうかを表しています。(safety 0)にすると実行時に型チェックを行いません。

Common Lispにはlocallyというスペシャルフォームがあり、部分的にdeclareすることもできます。通常は(safety 3)にしておき、ホットスポットのみ(safety 0)にしたりできます。Common Lispの便利なところですね。

関数のインライン化

関数自体は高速だけど、呼び出し回数が多い場合は関数呼び出しコスト自体が高くつくことがあります。

そういう場合は関数をインライン宣言することで呼び出しコストをゼロにできます。

(declaim(inline関数名))

ただし、インライン化するとプロファイル結果に出てこなくなりますし、デバッグもしづらくなるので適度に使うべきです。

コンパイル時までに計算できることはする

ここからはちょっと大変で、プログラムによって最適化の方法が変わってきます。

一つの方針として、「コンパイル時までに決定できることは決定しておく」という方法があります。

たとえば以下のような関数があるとします。

(defun invoke-callback (parser name)(let((fn (symbol-function(intern(concatenate'string"PARSER-"(symbol-name name))) parser)))(when fn
      (handler-case(funcall obj)(error(e)(princ e *error-output*))))))(invoke-callback parser 'body-cb)

実行時にシンボルをinternしてfuncallしています。しかし、引数が定数なので、internまではコンパイル時に解決できます。同等のマクロとして再定義したほうが高速です *3

また、もっと小さな範囲で解決したい場合はリードマクロの#.が使えます。

これは、後に続く式をリード時に実行し、その結果を式に埋め込むものです。リード時に解決できるものならこちらが使えます。

(=byte #.(char-code#\A))

(番外編) 高速なライブラリ

せっかく高速なプログラムを書いたのに、使っているライブラリが遅いせいで全体が遅くなる、という場合があります。そのため使うライブラリが高速かどうかを知っておくことは重要です。

たとえば以下の3つのライブラリは高速に動作します。

ただし、fast-ioを使えば必ずしも高速というわけではなく、長さ不定のバイトベクタを作る場合などに効いてきます。

XSubseqはsubseq + concatenateを連続で行うような場合に使えます。

JSONライブラリではcl-jsonやyasonなどがありますが、これらは拡張性を優先しているため遅いです。高速なJSON処理が必要な場合はjsownが使えます。

高速なライブラリを見分けるには

ライブラリが高速かどうかを見分けるのに僕が見ているポイントなどを書いておきます。

  • クラスを使っていないか

クラスを使っているライブラリは実行速度より拡張性を優先しています *4

flexi-streamsを始めとしたGray Streamsはメソッド呼び出しを行うので遅いです。

ベンチマーク実行用のコードがあると、そのライブラリは速度を重要視して書かれている可能性があります。

おわりに

僕はこんな感じで高速なプログラムを書いています。

他の言語だと高速なプログラムはCで書いてFFIで繋ぐみたいなことをしないといけないけど、Common LispだとCommon Lispで書いても高速なのがいいですね。

最適化って、努力した分だけ利くってわけでもないのでつらいですが、最終的に高速なCommon Lispコードが書けるとかなり脳汁が出るのでおすすめです。

参考

*1:Property listやAssociation listのような、オブジェクトをlistで代替する方法もありますが、言うまでもなく遅いので無視します

*2:SBCL 1.2.4では-4611686018427387904〜4611686018427387903

*3:同等のことをコンパイラマクロですることもできます。関数として定義しておきたい場合はコンパイラマクロを使います。

*4:もちろん、クラスを使っていてもコンパイル時に走るコード (マクロ内でしか使われていないとか) なら問題ありません。

サムライト株式会社に入社しました

$
0
0

somewrite logo

本日11月17日、サムライト株式会社プログラマとして入社しました。

サムライトはCommon Lispという最先端の技術を使う数少ないWeb企業です。

今月の頭までどこかの会社に雇用されるなど想像もしていなかったことです。

それがつい2週間前、兼ねてからTwitter上でやり取りをしていた @Rudolph_Millerが高速な広告配信サーバをCommon Lispで書きたいと言って人を募集しているのを見たことをきっかけに、社員で入る気はなかったにせよ何か共同で開発すれば僕もいくらか助けになるかもしれないと思ってすぐに会う約束をし、対面で詳しい話を聞くにつれ、いよいよ入社してフルコミットしたほうができることも多いだろうと考え、終には「いつまで居られるかわかりませんよ」という念押しをしつつも、一時振るう刀をこの会社に預けることにした次第です。



所感。

この会社には何もありません。従業員は20人程度でありながら、サービスは手広く大胆なほど。その彼らが手狭なオフィスで18個しかない椅子を取り合うように座っている。

さらに驚愕すべきことに、現在プログラマは2人しかいない。しかし、その彼らも孤軍奮闘という悲嘆にくれた様子もなく呑気にCommon LispのBaaSを立ちあげたいなどと放胆なことを言う。しかもそれがCTOと言うからさらに驚きである。

今後、僕はこの会社と共にCommon Lisp界、Webの世界を沸かせることをやります。

これを読んでいる人で、現職で自分でなくともできるような仕事に不満を感じている人。これから大学を出て名を売りたいと思っている大学生。無能で保守的な上司に飽き飽きしている人。 この会社では退職者が遺したクソに縛られることもありません。この会社で一緒に面白いことをやりましょう。

舞台はあります。この舞台で一緒に踊ろうという気概のある方は @nitro_idiotまでご一報ください。


高速なCommon LispのWebサーバ「Woo」を作りました

$
0
0

ここ一ヶ月ほど手掛けていたCommon LispのWebサーバ「Woo」が一応の完成に至りましたのでお知らせします。Clack-compatibleなAPIになっており、現状運用しているClackのWebアプリケーションでそのままお試しいただけます。

高速であることを最優先に設計しており、Hunchentootの4倍、Wookieの3.5倍高速に動きます。現状ではCommon Lispのサーバでは最速ではないでしょうか。*1

Benchmarks

いくつかのCommon Lispのサーバと、Node.js、Go、Pythonのサーバを比較してみました。縦軸はreq/secで、高いほうが多くのリクエストを捌けることを意味します。

f:id:nitro_idiot:20141219152449p:plain

Wooは、PythonのTornadoより約9.5倍、Node.jsの約1.9倍のリクエストを捌けます。一方、Goには少し負けています。

この結果を以ってNode.js遅すぎとか言うつもりは全くなくて、むしろNode.jsがRubyPythonと比べて2倍以上の差をつけているのは純粋に驚きました。Node.jsが速いと言われるのもわかります。

複数CPUコアを有効に使えるように、Workerプロセス数を指定したベンチマークも取りました。

f:id:nitro_idiot:20141219153408p:plain

こちらではRubyUnicornをnginxの裏に置いて試してみました。WooはUnicornの約2.4倍以上のパフォーマンスが出ています。Goよりはさらに差がつけられてしまっていますね。子プロセスへのディスパッチをまともにやらなければ。

ベンチマーク方法などの詳細はWooのリポジトリのBenchmarkをご覧ください。

Installation

インストールには以下のものが必要です。

その後、GitHubからWooをcloneしてbenchmark/woo.lispを実行するとサーバが立ち上がります。

$ mkdir -p ~/common-lisp && cd ~/common-lisp
$ git clone https://github.com/fukamachi/woo
$ sbcl --load woo/benchmark/woo.lisp

Why so fast?

WooがHunchentootやWookieに比べて圧倒的な差をつけているのはなぜ?と聞かれます。

最も大きいのは、non-blockingなサーバであり、さらにWookieと違って裏側でlibevを使っていることが大きいです。これで1.5倍くらいは違いますね。 *2

その他はアプリケーションレベルのボトルネックを丁寧に一つ一つ取り除いていきました。

おわりに

WooはGitHubにて公開されています。フィードバックをお待ちしています。

また、Wooは雇い主であるサムライト株式会社の全面的な協力を得て完成を迎えました。現在サムライトではCommon Lisperを募集しておりますので興味がある方は以下のWantedlyからご応募ください。

*1:高速なものとしてteepeedee2が有名ですが、CFFIのAPI変更に追随しておらず動かないようです。同僚が修正を加えた上でLinuxで試したところ、Node.jsより少し速い程度だった、と言っていたのでたぶんWooのほうが速いんじゃないかな

*2:ベンチマーク時のWookieはlibevent2を使っており、現状はlibuvに移行しています。どちらもlibevよりオーバーヘッドが大きく遅い。

Clack Meetup #1 を開催します

$
0
0

f:id:nitro_idiot:20150120165947p:plain

3月5日 (木) に「Clack Meetup #1」というイベントをサムライト株式会社のオフィスにて開催します。

Clackは拙作のCommon LispにおけるWeb application environmentであり、4年前にリリースされてから先日ようやくv1.0がリリースされました。

Clack MeetupではそのClackと共に、その周辺のエコシステム――WebフレームワークやDBアクセスライブラリ含む――の、Common LispのWeb開発全般の話を扱う予定です。

サムライトでの導入事例など、実際に動いているプロダクトの話も少しする予定なので、興味がある方はぜひご参加ください。

「The Healthy Programmer」を読んで自宅を快適なプログラミング環境にする

$
0
0

柄にもなく健康の話をします。

去年の今頃は京都から東京に引っ越してきたところでした。まだはてなで働いていましたが、勤務形態は自宅からのリモートが主になっていました。週に6日は家から出ずに家でコードを書いていました。昼から早朝まで書いて昼からまた早朝までコードを書くような、ある意味規則的な生活です*1

この間、生産性はかなり上がっていて、起きているときはもちろん、寝ている間も脳が活動を続け、睡眠中に考えたアイデアを唐突に起き上がってホワイトボードに書き込むみたいな状態でした。

しかし、1ヶ月くらいすると体調が悪くなってきます。なんか左脳の後ろ側が死んでるみたいな。

あと心臓が異常にバクバクする。座るのもきつくなってくるのでだいたいダメ人間ソファに沈んでHHKB叩いてるみたいな様子でした。

そして生産性も下がってくる。

リモート勤務を行うチームのマネジメントの難しさは最近よく語られるところですが、リモート勤務をする個人の話はそれほどは多くありません。特に健康についての難しさの記事とか見たことがありません。

新しく入った会社でも基本自宅でのリモート勤務なので快適な反面、健康問題は続きます。

The Healthy Programmerを手にする

そんなとき知人に推薦されて「The Healthy Programmer」という本を知りました。

Kindle版があったので早速買いました。3分の1ほど読んだあとにしばらく積ん読になっていましたが、最近改めて手にとって通読しました。

コードを書き続けるために健康になる

健康系の情報ってだいたい「運動しよう」とか「ダイエットしよう」とか「血圧を下げるには」とかいうものが多くて、むしろ栄養も血圧も筋力も足りてない系プログラマな僕には合いません。筋トレとか辛いし絶対したくない。家でじっとしてたい、みたいな。

The Healthy Programmerの違うところは、プログラマに特化していて、「コード書き続けたいよね? じゃあできるところからでいいから生活を改善してみない? ずっと座ってると腰痛くなったりして生産性落ちるじゃん?」みたいな気楽な感じです。

「健康になる」みたいな曖昧な目的ではなく、「コードを書き続けるために」という目的が一貫しているので好感が持てます。説教臭くないのも良い。

Exercise can increase your creativity, attention span, and ability to remember new concepts. Furthermore, reducing pain can help you focus and concentrate better.
運動はクリエイティビティを上げ、注意力を持続し、新しい概念を習得する能力を上げます。さらに、苦痛を減らすことは集中力を上げてより専念できることに繋がります。

表現もプログラマっぽく書かれているのも少し面白い。「健康をリファクタリングする前に、まずは自分の健康をユニットテストしましょう」とかナチュラルに出てきてちょっと笑えます *2

運動 ≠ つらいもの

でも、「そうは言ってもきつい運動とか毎日できる気がしないんですよね〜」と思いましたよね。

生まれてこのかた筋トレなんてしてないし、一番筋肉がある部分って心臓と横隔膜なんじゃね、っていう有り様なので、運動は嫌です。

そうやって身構えていたのですが、最初に出てくる運動が「散歩 (Walking)」です。

Walking is a powerful activity. It can stimulate creative thinking and it's the best way to bootstrap your health.
散歩はとても効力のある運動です。創造性を刺激し、あなたの健康をブートストラップする最良の方法です。

「1日8時間も座り続けている」というのがどれだけ健康リスクがあることなのかを説明し、「20分以上座り続けていてはいけない」と言っています。足を動かさないと足に流れた血が上半身まで戻って来づらくなるため、血の巡りが悪くなって脳に血液が回りづらい状態になるため、脳の働きも悪くなります。

歩くだけでなく、単に立つのも良いとされています。ずっと同じ姿勢をしているのが非常に良くないということだそうで。

立つ (Standing)

最近はIT企業でもスタンディングデスクを導入しているところもありますね。座り疲れたら立った状態でMacBookをカタカタやるやつです。

なので自宅にもカウンターテーブルを置きました。

AY ブルノハイテーブル AT-125CT(BR)

AY ブルノハイテーブル AT-125CT(BR)

こんな感じ。

f:id:nitro_idiot:20150211115614j:plain

ただ、このカウンターテーブルはスタンディングデスクとして使うには高さが少し足りなくて、スタンディングデスクするときは適宜本を敷いています。

f:id:nitro_idiot:20150211150231j:plain

立ち続けるのも良くない

とはいえ、ずっと立ってるのも良くないようです。膝に負荷がかかる。

Dr. Hedge recommends sitting for no longer than twenty minutes, but you shouldn't stand for longer than twenty minutes at a time, either.
ヘッジ博士は20分以上座り続けないことを推奨していますが、同時に20分以上立ち続けるべきでもありません。

立ったり座ったりを繰り返すのが良いようです。そのためにMacを移動するのも面倒なので、カウンターテーブルで立ったり座ったりする、というのをおすすめしています。

最近はスタンディングデスクやバランスボール (Exercise ball)が純粋に良いもののように語られることが多いですが、それも必ずしも良いというわけではない、たとえば足や腰を痛める可能性があったり、高血圧の人には悪影響、などの公平なアドバイスもあります。

ちなみにWobble Boardという、"スタンディングバランスボール"とでも言うべきようなものもあるようです。

絶対家にあってもやらないと思ったのでこちらは買っていません。会社にあると面白いかも。

サイクリング (Cycling)

The Healthy Programmerの論調として、「○○をしたら健康になれる」というのはありません。だから歩けば良いとか、立てば良い、というわけではなく、重要なのは同じ姿勢を20分以上続けないこと

  • Work from at least three different positions every day.
  • Don't stay in any one position for longer than twenty minutes.
  • Keep more than one kind of chair your office -- a regular office chair, an exercise ball, a drafting stool, or even a cycling desk.
  • 毎日最低でも異なる3ヶ所から仕事をする
  • 20分以上同じ場所に留まってはいけない
  • オフィスでは1種類以上のイスを使うこと -- 普通のオフィスチェア、バランスボール、可動式のイス、さらに言えばサイクリングデスク

リモート勤務する僕にとっては自宅がオフィスです。普通のイスとカウンターテーブルは用意しました。

f:id:nitro_idiot:20150211120245j:plain

あと一個か…。

というので、エアロバイクを買ってみました。

ALINCO(アルインコ) クロスバイク AFB4513

ALINCO(アルインコ) クロスバイク AFB4513

f:id:nitro_idiot:20150211121017j:plain

ALINCOという会社が有名のようで、その会社の折り畳み式のエアロバイクです。「ジムのものと比べて負荷が軽すぎる」とか「30分以上連続稼働できない」とか書かれていますが、僕の場合は筋トレをしたいわけではないので問題ありません。っていうかこれ3分くらい漕いだら普通に疲れるんですが…。

漕いでいるときの音も異常に静かで、加湿器とかエアコンの音にかき消されて聞こえないくらいです。コンセントに繋ぐ必要もないし、来客のときは移動したり折りたたんだりできる。

リビングに置くのが割と重要だと思っていて、寒い外に出なくても、短時間でも気楽に運動できるのは良さそうです。

まとめ

The Healthy Programmerを読んだし、少し広めの家に引越したついでにいろいろ家に投資してみるか、と思って導入してみた話でした。

最近IT業界でも健康について語られることもありますが、「なぜ良いのか」とか「デメリットはあるか」とかがあまり語られず、曖昧に運動したほうがよさそうという程度にしか語られません。

その点The Healthy Programmerは最近の研究に基いて良いところ・悪いところを公正に書いているので読んでいてすっきりします。

今回は自宅の改善に関連するところだけ取り上げましたが、この本自体は幅広い内容を扱っています。

興味があれば読んでみてください。Kindleで読めます。

追記: 翻訳版が今年の夏頃にオライリーから出版予定のようです。日本語で読みたい方はそちらを待つと良さそう。

追記: 日本語版が出たようです。

ヘルシープログラマ ―プログラミングを楽しく続けるための健康Hack

ヘルシープログラマ ―プログラミングを楽しく続けるための健康Hack

*1:この頃に生まれたのがLesqueIntegralCL21です。

*2:著者のJoe Kutnerさんは現役プログラマのようで、表現の選択のセンスも割と良い。

Common Lispとリアル・ワールドを繋ぐ「Roswell」の紹介

$
0
0

Roswell」というプロダクトがある。

clfreaksPodcastで聴いたことがあるかもしれないし、数週間前にLisp Meet Upで佐野さん (@snmsts) の発表を聞いたかもしれない。

知名度の割には意外と長く開発が続いているプロダクトだ。プロジェクトの最初のコミットが2014年7月30日なので、もう11ヶ月ほど開発が継続していることになる。開発は未だ活発で、バグ報告をするとその日のうちに修正がmasterに入ることが多い。

僕が働いてるサムライト株式会社では、個人の開発マシンではもちろん、アプリケーションサーバでも日常的に使われている。

RoswellにはChefのrecipeも既にある。

先日RoswellはTravis CIとCircle CI用のインストーラスクリプトも提供し始めた。これにより、CIサービスでRoswellを使ってCommon Lispプロダクトを継続テストできるようになった。

さらに、Common Lispのいくつかのプロダクト――Clack、Qlot、Woo――はRoswellユーザ向けに便利なスクリプトを提供している。

このようにRoswellの適用範囲は増えていく一方だ。

人々の声

f:id:nitro_idiot:20150611091538p:plain

Roswellはじわじわと現実世界に染み込んでおり、もはやこれなしで開発することは苦痛でしかないと感じる。

しかし、この温度感はclfreaksのメンバー以外にはまったく伝わってないと思う。それは佐野さんの控えめなマーケティング戦略のせいかもしれないが *1、それだけでなく、Roswellがあまりに急速に我々の間に広まって当たり前になってしまったために、あえて今更言及するほどでもない存在になってしまったからでもある。

この記事では改めてRoswellというプロダクトの紹介をする。

Roswellのインストール

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

$ brew tap snmsts/roswell
$ brew install roswell

その他のOSはREADMEを参照。

Roswellをインストールするとバイナリ版SBCLとQuicklispがインストールされる。

Common LispインストーラとしてのRoswell

RoswellはCommon Lisp処理系のインストーラとして開発が始まった。RubyのRVMやrbenv、PerlのPerlbrewなどを想像するとわかりやすい。

$ ros install sbcl
$ ros install sbcl/1.1.8  # バージョン指定
$ ros install ccl-bin     # Clozure CL

CIMと違ってSBCLとClozure CLにしか対応していないが、手広くない分この二つの処理系では問題なく動くことが期待できる。

インストールされた処理系、バージョンは切替ができ、rosで起動されるCommon Lispを変更できる。

$ ros run -- --version
SBCL 1.2.12

$ ros use sbcl/1.1.8

$ ros run -- --version
SBCL 1.1.8
$ ros run
* (+ 1 2)
3
* (lisp-implementation-type)
"SBCL"
* (lisp-implementation-version)
"1.1.8"

インストールされているLispros list installedで確認できる。

$ ros list installed
detail shown by
ros list installed [implemntation]

implementations:
    ccl-bin
    sbcl-bin
    sbcl/1.1.8
    sbcl

RoswellがCommon Lispとシェルを繋ぐ

Common Lispインストーラというだけなら環境構築を楽にしてくれるツールというだけで話は終わる。

しかし、Roswellは意図せず二弾式ロケットのようになった。その二弾目の機能が「シェルとの連携」だ。

今までCommon Lispでポータブルなスクリプトを書くのは難しかった。処理系によってコマンドライン引数が異なるからだ。

$ sbcl --load script.lisp --eval '(sb-ext:exit)'
$ ccl --load script.lisp --eval '(quit)'
$ clisp script.lisp

これに対しRoswellではrosというコマンドでラップされており、どの処理系も同じように扱える。

$ ros -l script.lisp

Roswell Script

これだけではない。.lispのようなファイルではなく、シェルコマンドとしてCLであることを意識せずに使えるスクリプトも書くことができる。

ros init [スクリプト名]を実行するとスクリプトのひな形が生成される。

$ ros init hoge
Successfully generated: hoge.ros

この中身はこのようになっている *2

$ cat hoge.ros
#!/bin/sh
#|-*- mode:lisp -*-|#
#|
exec ros -Q -- $0 "$@"
|#
(defun main (&rest argv)
  (declare (ignorable argv)))

この「.ros」という拡張子のついたスクリプトは「Roswell Script」と呼ばれており、既にいくつかのプロダクトで使われている。

Clackはサーバ起動スクリプトとして「clackup」を、Qlot *3はラッパースクリプトqlot」を、Wooは自身の実行可能ファイルインストーラinstall-woo」を提供している。

これにより、Common Lispスクリプトを配布するときの手間がかなり省けることになる。単にRoswellをインストールしてください、で説明は終わる。

このRoswellの副次的な機能はShellyを殺しつつある。僕はこの変化を歓迎している。サムライトではShellyを使っていないし、今後移行する予定もない。Roswellを使おう。

まとめ

Roswellはインフラだ。今まで不可能だったことを可能にするようなプロダクトではないが、我々の生活をより良いものにしてくれる。いずれは入門書のセットアップに必ず出てきて「常識」と呼ばれるようになるだろう。

*1:Roswellの現在のバージョン番号は0.0.2.33。いつになったらバージョン1.0を迎えるのか。

*2:shebang行が #!/bin/shだが、exec rosによりrosコマンドに入れ替わる。このハックの詳細は「割と処理系ポータブルなCommon Lisp実行可能ファイルを作る」で解説されている。

*3:プロジェクトローカルにQuicklispをインストールするツールhttps://github.com/fukamachi/qlot

Lisperはプログラムに何を見るか

$
0
0

男子校に通う中学生の僕らにとって「家庭科」の授業は休憩時間のようなものだった。

僕の中学校には家庭科室というものがない。だから、いつもの教室で野菜の種類やそれに含まれる栄養素なんかを教わるというだけの、正直退屈な授業だった。話される内容はどれもただ暗記すればいいものなので、授業を聴かなくても定期試験前に教科書を読み通すだけで九〇点は取れる教科だった。

学校としても文科省の教育課程に沿うがためだけに時間割にねじ込んでいるに過ぎなかったと思う。特別教室がないことでも真面目にこの教科を取り扱う気がないことがわかるし、生徒の方でもその学校の態度を敏感に感じとっていた。

そんなやる気のない男子学生の前に立って話すのは教師にとって楽しいものではなかっただろう。僕らの先生は、落ち着いた雰囲気でどこかしたたかさのある、髪の長い女の先生だった。

その日も彼女はいつも通り、キノコに含まれる何々という栄養素が、体内でビタミンDになるという話をしていた。ふてぶてしくも一人のクラスメイトが先生に訊ねた。「こんなこと覚えて意味あるんですか」

すると先生は一瞬黙り込んでから視線を手元の教科書に落としたままひとりごとのように答えた。「意味のない知識が多いほど、人生は面白いの」

先生はそれ以上は言葉を加えず、再び何々という栄養素に話を戻した。

最初、僕は先生の言葉の意味を単純に知識欲を満たすことができるからだと思った。けれど、彼女が本当に言いたかったことは、もっと深い。知っているのと知らないのとでは、ものの感じ方が違う。同じものを見ていても見ているものが違うんだ。

知っている花が多ければ散歩中に見つけた花で季節を感じることができる。歴史を知らない人にとって関ヶ原はただの荒野でしかないけれど、知識があればそこに武者たちの壮大な物語も見ることができる。

彼女の授業で今でも覚えていることと言えば、レモンが評判ほどにはビタミンCを含まないという程度のことだけど、その日の彼女のその言葉だけは今でも鮮明に覚えている。

自分にしか見えないもの

逆に、自分には見えていて他人には見えていないものもある。

最近、妻がプログラミングの勉強を始めた。彼女は都内のWeb企業に勤めてはいるがプログラマではなく、仕事上求められるわけではない。ただ単純にプログラミングはどういったものか、何ができるのか、ということを知りたいという純粋な好奇心から学ぶことを始めたようだ。

最初に学ぶプログラミング言語は何がいいかな、と彼女は訊いた。僕は、JavaScriptが良いだろうね、と答えた。特別難しい言語ではないし、ブラウザという身近な処理系がある。何より学べば何かしら役にも立つかもしれない。

けれど、入門書という面ではこの言語は恵まれない。僕自身、良いと思ったJavaScriptの本は2冊しかない。そのうちの一つの「JavaScript第5版」が家にあったので妻に与えてみたが、ほんの数章で読むのをやめてしまった。

それから僕と彼女は池袋のジュンク堂に行って、技術書コーナーを行ったり来たりすることになった。さまざまな言語の入門書をめくってみて彼女に最適なものを一緒に探した。

ある本は難しすぎ、ある本は内容が浅すぎた。これは、と思う本を手にとって彼女に意見を求めてみても、内容が難しい、と言われる。良い本かどうかの判断はできるが、何を難しいと思うのかは僕にはわからない。

最終的に選んだのは「初めてのPerl 第6版」だった。この本は僕が最初にプログラムを学んだ本でもあり、文章も面白くて読み進められる工夫がされている。他の言語を後に学ぶにしても、最初はPerlで入門してからなら理解も深まるかもしれない。

プログラミング学習の難しさ

僕のことを知る人は、僕の妻がCommon LispではなくPerlを学んでいると聞いて不思議に思うようだ。

自分の妻にCommon Lispを勧めない理由の一つは、彼女がその恩恵を受けるほど複雑な問題を解いたり、ある程度の規模のプログラムを書くという機会はないだろうという推測からだが、他にもCommon Lispを学ぶのは他の言語に比べて全く簡単ではないだろうという考えからでもある。

Lispは構文上のルールが少ないので、学ぶのが簡単だと言う人もいる。僕自身も少し前までそう考えていた。けれど、学習の容易さは覚えるべき文法ルールの多少によるものじゃない。プログラミング言語の習得の難しさは、その言語が提供する抽象化の概念の理解の難しさに比例する。

プログラミング言語はもともとコンピュータを抽象化する目的で作られた。人間が0と1の組み合わせでプログラムを書かなくてもいいようにアセンブリ言語ができたわけだし、さらに高等な命令を持つCのような言語も流行した。プログラミング言語の進化は概ね、より人間に優しい言葉になる進化だ。

その後、プログラミング言語はもっと高度な抽象化によってプログラムを簡単に書けるように進化した。それによって決して単純ではなくなるが、一度学ぶと簡単にプログラムを書けるようになるといった類の改善だ。オブジェクト指向がその一例だろう。Cがアセンブリ言語に対して行う抽象化とは違い、オブジェクト指向は非連続な言語の進化だ。オブジェクト指向の概念は、明らかにCのfor文より高度な抽象化だ。

進歩したプログラミング言語ほど、このような抽象化された概念が多い。――レキシカルクロージャ再帰関数、Cのポインタ、Perlのコンテキスト、Schemeの継続。そして、高度な概念を扱う言語ほど学ぶのが難しい。

その中でもLispは学ぶのが難しい部類だと僕は思う。事実、プログラミングの全くの初心者だけでなく、他の言語で十分な経験がある人でもLispを前に容易に降参するのを見た。僕自身、Schemeを学ぶ段階でPerlPHPJavaScriptの経験があったけれど、他の言語を習得するのに比べて多くの苦痛が伴った。Lispを学ぶことを困難にしているものは何だろうか?

この問いは、本にかじりついて学ぶ過程をくぐり抜けた僕にとっては無用ではあるけれど、今後Lispを学ぼうという人が挫折するだろう石ころを事前に取り除けるというならそれに越したことはない。何しろ僕には、いずれ妻にCommon Lispを教えなければならない日が来るかもしれないのだから、今のうちに考えておきたい。

Lispは今まで多くの言語に影響を与えてきたため、Lispが持つ多くの概念は他の言語の習得者にとって既に真新しいものではなくなっているはずだ。代表格と言える無名関数はもはやほぼすべての高級言語に含まれている。けれど、他の言語が真似できない、最もLispの中で重要な概念をそのカッコに隠している。Lispではそれを「マクロ」と呼ぶ。

マクロ: プログラム可能なプログラム

Lisp is a #1=(programmable . #1#) programming language.

マクロはプログラムを書くプログラムである。

人によってはこれを聞いただけで恐怖心を感じて考えるのをやめてしまう。そんなものいつ必要になるんだ? ――ほとんどいつでも。ただ、ここではその議論に立ち入るのをやめよう。深呼吸して、ただここではLispにそういった機構があるという事実だけを知っていればいい。

通常のプログラミング言語では、実行されるコードをプログラムに記述する。これを一層目のプログラムだとしよう。Lispではさらにその一層目のプログラムを生成する二層目のプログラムを書くことができる。このようなプログラムはメタプログラムと呼ばれる。

Lispが異常な点は、そのメタプログラムをユーザが自由に書けるところだ。メタプログラム内ではフルのLispの機能が使える。もちろん自分で定義した関数を使うこともできるし、ライブラリも使える*1。こういうプログラムが欲しい、と書けば、書くべきプログラムをLispが代わりに書いてくれる。

そしてこの層の連鎖は無限に続けることができる。つまり、プログラムを書くプログラムを書くプログラムを書くプログラムを書くこともできる*2Lispはプログラム可能なプログラミング言語なのだ。

なぜLispプログラマにとってマクロはそれほど重要なのか? それは単にマクロが提供する文法上の便利さなどではなく、マクロがプログラムを操作するプログラムであり、それがLispコミュニティの中心的テーマであり続けているからだ。FORTRANが数を、Cが文字とポインタをこき使う言語ならば、Lispはプログラムをこき使う言語なのだ。

Why are macros so important to Lisp programmers? Not merely for the syntactic convenience they provide, but because they are programs that manipulate programs, which has always been a central theme in the Lisp community. If FORTRAN is the language that pushes numbers around, and C is the language that pushes characters and pointers around, then Lisp is the language that pushes programs around.

Quoted from 「The Evolution of Lisp」by Guy L. Steele Jr. & Richard P. Gabriel

マクロを持つために必要なもの

何も僕は、Lispを学ぶならまずはマクロを学ばないといけないなんてひねくれたことを考えているわけじゃない。僕が思うのは、Lispにはマクロがあるからこそ他の言語にはない高度な抽象化を行っているために、それがLispを理解する妨げになっているんじゃないか、ということだ。

Lispはより妥協の少ない哲学をもっていて、非常に強力でよく統制のとれた 言語の核を提供している。このことは Lispを学ぶのをむずかしくしている。 なぜなら最初から高水準の抽象化を扱わければならず、見た目や感覚に 頼らずに自分がいま何をしているかを理解しなければならないからだ。

Quoted from「LispプログラマのためのPython入門」by Peter Norvig

じゃあ、Lispが提供する高度な抽象化とは何か。

一般に「Lisp」と呼ばれている言語の特徴は「プログラムをその言語自身の平易なデータ構造で表すことができる」ことだ*3

この特徴は「マクロ」と無関係ではない。プログラムを生成するプログラムを書くためには、プログラム自身がプログラムから扱いやすいものでなければならないからだ。

Lisp方言の一つであるCommon LispのコードはCommon Lisp自身の平易なデータ構造で表せる。式は「リスト」で表されるし、変数は「シンボル」というデータ型で表される。他の言語で変数を表す「変数型」のようなものがないことを考えても、やはりLispは特異だと言える。

自然、Lispプログラムには、「プログラミング言語としてのLisp」と、「メタプログラミング言語としてのLisp」が混在することになる。プログラマは、プログラム中のそれぞれの部位がどのタイミングで評価 (evaluation) されるかを意識しながらプログラムを書く。この制御にはクォートを使う。これも、マクロを実現するためのLisp特有の機能だ。評価する機能 (eval) はありふれていても、評価を抑制する機能がある言語が他にあるだろうか。

熟練のLisperたちは日常的に入れ子になった式の評価タイミングを意識して、息を吸うようにクォートを使いこなす。Lispを知らない人にとって平坦に見えるプログラムも、Lisperにとっては確かな立体感を持って知覚される。

おわりに

あの日家庭科の授業で生徒が正直過ぎる無礼さで質問をぶつけたとき、先生はこう言うこともできたはずだ。将来一人暮らしをしたときに自炊をしなければならないでしょう。そのとき自分の栄養管理をするのはあなた自身なのよ。けれど、そんな説教くさい話では生意気な男子中学生の態度を改めさせることなどできなかったに違いない。

Lispは学ぶべき言語だろうか。僕も説教くさい話はやめよう。Lispは楽しい。それを共感できる人間は、多いほうがいいけれど。


この記事の下書きを読んで意見をくれた同僚のRudolph MillerMasayuki Takagi@yanqirenshi、そして妻の@meymaoに感謝します。

*1:この利点を示すわかりやすい例として、プログラムを生成するときにハッシュテーブルを使って効率的なプログラムを生成するようなメタプログラムを書いたりできる。このアイデアは僕の書いた高速なHTTPパーサ「fast-http」で実際に使われている。

*2:これは必ずしも連続したプロセスである必要はない。Common Lispではコンパイル時に実行することもできるし、実行時にコンパイルすることもできる。

*3:最初期のevalを持たないLISPは完全なLisp実装ではない。現代の感覚で言えば最初期のLISPFORTRANのリスト操作ライブラリと言ったほうが正確なものだったろう。また、敢えて「平易な」と入れているのは誰かが「◯◯の言語でも構文木をデータとして扱うことができる」と言い出すのを見越してのことだ。Lispの場合プログラムはリストというポピュラーなデータ構造で扱える。これはコンパイル時にもそのプログラムに対して標準のリスト処理関数が存分に使えるという利点がある。

Viewing all 74 articles
Browse latest View live