2010/08/16
アナリーゼを習いたい
ひととおり音符を追えるようになったら、 何か違う景色が見えるかもしれないと思い、 8年くらいかけて、ショパンのエチュード24曲を拙いながらもさらってみた。 出来上がったのは、退屈な演奏だった。 テクニカルな限界という、足に絡まる海藻をちょっとだけでも振り切って やっと水面に顔を出してみたら、垣間見えたのは茫漠とした海原だったって感じだ。
(別にがっかりしてるわけじゃなく、 むしろ「ああ、こうして退屈な演奏というのが出来るのだなあ」と感心することしきりなのである。 あと、色々テクニカルなコツはわかってきた。 Op.10は6年かかったけど(うち2年ブランクだけど)、 Op.25は2年で済んだのは、進歩と言えるだろう。)
Moffettのアクティングクラスで学んだのは、 役者の仕事は、上手く演技することではなく、 脚本の「イイタイコト」をいかに効果的に伝えるかにある、ということだった。 表現の技術はその手段にすぎない。 どんなに上手くても、脚本のイイタイコトを理解せずに演技していたら、 それは失格だということだった。 そして、そのための脚本の読み方というのがある。
そのアナロジーで考えれば、 楽譜の中に込められた「イイタイコト」を読み解く方法を、自分はまだ知らない。
脚本の読み方を知った時、霧が晴れたように感じた。 良くできた脚本では、「イイタイコト」は太字の拡大フォントのステッカーで 脚本の最初から最後まで、あらゆる箇所にべたべたと貼り付けてある。 ただ、読み方を知らなければ全く見えないだけなんだ。
アナリーゼというやつを学んだら、音楽についても同じことが起きるんじゃないか、 と期待している。
2010/08/15
文字クラスのマッチ
正規表現の[]でくくった文字列クラス内の文字の並びがマッチ速度に 影響を与えるかどうか、という話題。
対象となるコードが1バイト(つーかオクテット?)の範囲に収まるくらいなら、 ビットベクトルを使うのが多分常道で、その場合はどう書いても実行結果は同じのはず。 マルチバイト文字やらワイド文字やらUnicode なんかが対象に入ってくると これはもう実装次第としかいいようがないわけで。 仮に対象を Unicode としてその大きさがほぼ100万文字というパターンを考えると、 ビットベクトルでこれを収めるには…と。 まあイマドキの環境だったらどうってことないのかなあそれでも。
GaucheではASCIIの範囲はビットベクタ、それ以降は連続する文字の「範囲」を treemapで管理してる。各範囲の下限をキー、上限を値として。 なので順番は関係なく、ルックアップ速度は「範囲」の個数nに対してO(log n)。
まあ、treemapは内部的に既に持ってたから使っただけで、 単純に範囲をソートして配列で持っておくだけでもバイナリサーチかけられるので、 そうしない手はないと思う。 なので「順序で影響が出るような実装を使うメリットが思い当たらない」かなあ。
範囲で管理するのは、不連続な小領域が大量にある場合にえらく不利になる。 実用上たぶんそんなことは滅多に無さそうだとは思うけれど、最悪値を 良くしたいなら、範囲の数が閾値を越えたらビットベクタに切り替えるって手はある。 全部ビットベクタにしなくても、ある深さまでは範囲で、その下は その範囲だけのビットマップ、というふうにもできるだろう。
話はずれるんだけど、Gaucheは文字列をマルチバイト表現 (デフォルトutf-8) で 持ってるので、正規表現エンジンは文字単位ではなくオクテット列に直接 マッチをかける (マッチ開始位置は常に文字境界なので、euc-jpやshift_jisでも 非文字境界でマッチしてしまうことはない)。 なので基本的に utf-8 <-> ucs-4 の変換は 起きないのだけれど、ASCIIをはみ出す文字クラスの時だけはこの変換を行わないと ならない。
でも、ucs-4での文字範囲は、utf-8で表現したオクテットの範囲のツリーに 変換できるはずなので、それをやれば完全にutf-8のままマッチがかけられる。 もう何年も前から試したいとは思っているのだけど手をつけられていない。 そういう実装をやってるエンジンってあるのかな?
Tags: Programming, Gauche
2010/08/03
sambaプチはまりメモ
Ubuntu 10.04に入れ替えたら、sambaで別マシンからつなげなくなってることに 気づいた。[homes] セクションでホームディレクトリを見えるようにしてるんだけど、 外のWindowsから \\server\shiro でマウントしようとすると ひたすらパスワードを聞かれるだけでマウントできない。 入れ替えるまでは問題無かったので、smb.confのデフォルトが何か変わったのかなと 調べてて余分な時間を費やしてしまった。
原因は、確かに新しいバージョンで変わったところにあったのだけど、 その変わった箇所というのが意外な盲点だった。たぶんこれではまるのは特殊例。 トラブルシューティングの手順をメモっとく。
- sambaのdebug levelを上げてログを取る。最初log level = 2 で
check_ntlm_password: Authentication for user [shiro] -> [shiro] FAILED with error NT_STATUS_NO_SUCH_USER
というのがわかったが、これでぐぐっても一般的すぎてあんまり役に立たない。 - log level = 3にしたら手がかりらしきものが。
check_sam_security: Couldn't find user 'shiro' in passdb.
自分のアカウントが登録されてない? - passdbってなんじゃい、と思って調べる。sambaは独自にユーザデータベースを 持ってるのか。デフォルトでは /var/lib/samba/passdb.tdb というのを 見てるらしい。こいつの中身を覗くにはpdbeditというコマンドが使えるようだ。
- pdbedit -L すると確かに、他のユーザのエントリはあるのにshiroがないぞ。 他のユーザのエントリは多分インストール時に/etc/passwdから情報を取ってると 思うんだが、なぜshiroだけ抜けたのか。
- ひとつ怪しいのは、shiroのuidが500であること。最近のLinuxでは 一般ユーザのuidは1000から割り当てるけど、昔はもっと小さいuidを 使ってたのだ。500というのはたぶん10年以上前にLinuxをインストール した時に割り当てたuidで、OSを入れ替えても ファイルシステムをそのままアクセスできるようにずっとキープしていたのだった。
- でも、最近のディストリビューションだとuid < 1000 はシステムアカウント 用だから、sambaインストール時のpassdb構築時に無視されたんではないか、と推測。
- ビンゴ。"passdb.tdb uid 1000" でぐぐって http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=502801 に至る。
- UID_MIN とかをいじって再インストールするのも面倒なので、 pdbedit -a -u shiro として直接エントリを加えた。 これで解決。
uidを変えるべきかなあ。でもいくつもあるマシンを非同期に アップグレードするから、一斉に変えるのって面倒なんだよなあ。 かといってディレクトリサービス上げるほどの規模でもないし。
2010/08/03
ヘッダにstatic member
C++の class template を使えば static メンバの実体がヘッダファイルに書けるカラクリ - ひげぽん OSとか作っちゃうかMona
C++の class template を使えば static メンバの実体がヘッダファイルに書けるというテクニックがある。考えてみると不思議な動作に思える。だって分割コンパイルしたら実体が複数個出来そうじゃない?。この裏側で起こっている事を実験前に予想して試したところ、予想通りだったのでうれしかったのでメモを残す。
リンカがよしなに計らってくれるのか、なるほど。と思ったけど じゃあリンカの預かり知らぬところで共有されたらやばそうだよなあ。 例えばdlopenされるやつとか。
環境はgcc-4.4.3 on Ubuntu 10.04。
Hoge.h と a.cpp はひげぽんさんのと同じ。 b.cppはdlsym経由で 呼びやすいようにC linkageにしておく。
b.cpp:
#include "Hoge.h"
extern "C"
void funcB()
{
Hoge<bool> hoge;
hoge.incrementAndShow();
}
mainはこんなかんじ。
main.cpp
#include <dlfcn.h>
#include <stdio.h>
#include <errno.h>
extern void funcA();
void (*pfuncB)();
int main(int argc, char *argv[])
{
funcA();
void *h = dlopen("./b.so", RTLD_NOW);
if (!h) { perror("dlopen"); return 1; }
pfuncB = (void (*)())dlsym(h, "funcB");
if (!pfuncB) { perror("dlsym"); return 1; }
pfuncB();
dlclose(h);
return 0;
}
ビルドはこんなかんじ。
Makefile:
all : main b.so
main : a.o
g++ -o main main.cpp a.o -ldl
b.so : b.o
g++ -shared -o b.so b.o
b.o : b.cpp
g++ -fPIC -c b.cpp
clean :
rm -f main *.so *.o *~
実行結果。別々のHoge::counterの実体を見てる。
$ ./main counter = 1 counter = 1
まあ仕方ないっちゃ仕方ない話だけど。 外部APIとして見せるクラスでこのテクニックを使う場合は落とし穴になるかも。
(なお、b.cppを共有ライブラリlibb.soにしてmainリンク時に -lbでリンクした場合は、ちゃんとHoge::counterは共有される。)
理想的には、ランタイムがもっと賢くなって、 実行前の静的なリンク実行時の動的なリンクも同じように扱って くれればいいのかなあ。
Tags: Programming, C++
2010/07/28
Gaucheにdocstringが無い理由
gauche-develあたりで昔書いたような気もするけど、日本語でも書いとこう。
gauche はドキュメンテーションに不安があるな。EmacsLisp に慣れてるからだろうか。
コードのすぐ側にドキュメント文字が置けるのは大きなメリットだと思う。
Lisp系言語は昔から、関数の中にドキュメントを書けて、REPLからいつでも参照できる 機能を持っていた(docstring)。Gaucheは敢えて伝統を破って、その機能をつけていない。 今後もたぶんつけない。
主な理由はふたつ。
- Gaucheは効率的なスクリプトエンジンであることを第一の目的にしている。
つまり、毎回実行の度にソースが読まれてパーズされることが前提。
docstringは開発時には役に立つけれど、実行時にはただのオーバヘッドである。
(プリプロセスしてdocstringを外したスクリプトを作っておくという手はあるが、
プリプロセスするくらいなら事前にコンパイルしたって同じ手間で、
その手間をかけないからこそのスクリプトエンジンなわけで。)
ある意味コメントもそうなんだけど、コメントのパーズの方が文字列のパーズより軽い。
- そもそも、コードとドキュメントを近くに置くのは良くないと思っている。
- ちゃんとしたドキュメントを書こうとすると結構な分量になり、コードと混ざって コードがひどく読みにくくなる。例:大きなJavaのプロジェクトのソース。 (但し、Javaは設計方針としてコードを読むことよりも既にあるAPIを呼ぶことの方を 重視しているふうであり、そうであるならばコードの読みやすさを犠牲にしても それなりのAPIドキュメントが確実に生成されることを重視するのは理にかなっている。 Gaucheの方針は違うけど。)
- 読みやすいドキュメントというのは構成も含めて考えられるべきだが、コードに混ぜて 書こうとすると全体の構成がコードに引っ張られてしまう。(KnuthのWEBシステムは ドキュメントの構成が先にあり、コードはシステムによって並べ替えられる。) 個々の関数のクイックリファレンス程度なら関数ごとに独立してるからいいんだけど、 それとは別にちゃんと構成を考えたドキュメントはどっちにしたって必要。
- コードとドキュメントはひとつの抽象物を別々の視点から記述したもの。 より記述されるものを明確にするには、なるべく別の方向から記述した方が良い。 コードとドキュメントを一緒にすると、似たような視点からの記述になってしまがち。 (Cf. コードと別にドキュメントを書く意味)。
- これはワークフローによるけど、Gaucheみたいにドキュメントのソースが 多国語化前提の場合、コードに埋め込むのはやりにくい。(もっとも、ソース内に 全言語を持っておくというGauche方式は言語数が増えると破綻するので、 コード中のドキュメントは例えば英語で統一しといてgettext式に 翻訳を別の流れでやるって手はある。)
開発中にすぐにドキュメントが参照できる、というdocstringのメリットについては 別手段で代替可能だ。Emacsを使ってるならソースから一発でinfoドキュメントに飛べるし (例: http://blog.livedoor.jp/naoya_t/archives/51350496.html )、 REPLから (info '関数名) とする手もある。 info 関数については ページ単位ではなくピンポイントで関数の説明に飛べるように工夫したいと思っているけど。 要は、「すぐに引ける」というメリットについては、ドキュメントがどこにあろうが 対応関係が機械的に作れれば何とでもやりようがある。
ただ、問題が無いと思っているわけではない。
- 標準のドキュメントフォーマットがない。これは、未だ決めかねているという状態。 Gauche本体のように大きなものにはtexinfoが便利なんだけど、全てtexinfoでやれというのは 無理があるし。Perlがpodを、Rubyがrdを持ってるように独自に作っちゃう、っていうのも 車輪の再発明っぽくて億劫。わりと無節操に「色々なフォーマットが使えますぜ」っていうのも Gaucheっぽくてありかなとは思ってるんだけど。
- 1ファイルで済むようなちょっとしたスクリプトやライブラリなのに、わざわざ別ファイルに ドキュメントを書くというのは確かに面倒。そういう場合はやっぱり ひとつのファイルにまとめておきたくなる。 実行時に負担にならなければいいので、他のスクリプト言語にある __END__ のように loadは何らかのマーカーを見たらそれ以降は読まない、ってことにすればいいんじゃないかとは 思ってる。
(追記2010/07/29 15:55:38 UTC): 別の表現を思いついた。Gaucheの設計方針のひとつは、 SICPにある次の文である。
プログラムは、人が読むために書かれるべきであり、 それがたまたま機械でも実行できるにすぎない。
したがって、言語機能の選択基準の一つは、「いかにコードを読みやすくするか」 という点にある。コードの理解を助ける目的ならば、 関数毎にコメントやアノテーションでその意図 (whyの記述) や 事前/事後条件、トリッキーなコードの説明などを書いておくのは良いことだ。 でもdocstringはコードの理解を助けるための文書ではない。 それはAPIを使う人のための文書だから。
たまたま、APIを使う人のために最適な文書と、コードを理解するために最適な文書が 一致するということはあるかもしれない。でもそうでないことの方が多いだろう。
だとすれば必然的に、docstringとコードを同居させるには、「コードを読みやすくすること」 か「APIを使う人に役にたつこと」のどちらか、もしくは両方を犠牲にするしかない。
Tags: Gauche, Lisp, Programming

Comments (0)