tamaño de fuente: + - prefijar
RSS 1.0
G a t a  D u r m i e n t e  -  d i a r i o
Volver al índice
Firefox :: Faster Safer Cuter
Say No to corrupt Audio CDs
三次元なんかに興味あるか、ばーか。

Tópicos Recientes

* まもなく移轉
當サイトはまもなく移轉します。

* みっくみく事件の決着
驚いた。ドワンゴが殆ど完全に折れる形で、しかもこんなに早く決着が付くとは思はなかった。こんな結末になるとは夢にも思はなかった。

* Quốc ngữ と日本語
ベトナム語は六つの聲調を持つ言語であるが、現在それを表記する爲に用ゐられてゐる Quốc ngữ ではその六声調を書き分ける事が出來るらしい。

* 自然物と信仰/生體機械としての人間/初音ミク/オープンソース戰爭
みっくみくが JASRAC された件には非常にもやもやとさせられる。だから出來るだけ消化しようと試みた。Every man thinketh his burden is the heaviest.

* HsHyperEstraier 0.1
HyperEstraier の Haskell 用バインディングである HsHyperEstraier を公開した。

Lee más...

_ domingo, 18 febrero 2007
Cuenta Larga = 12.19.14.1.7; tzolkin = 3 Manik; haab = 0 Kayab [Trackback Ping]

 Smalltalk と言へば、私が今眩暈がするほどクソ難しい Haskell で四苦八苦しながら趣味コードを書いてゐるのは essa 氏が Smalltalk で呼吸してゐたのと似たやうな理由だと思った。壓倒的な物量と、日に日に増大するカオス。朝令暮改の仕樣變更と、その度に發生する無限のバグ。開發メンバーの中で全體を把握出來る者がつひに誰も居なくなること。『これは假實裝だから後で修正される』とコメントで警告したはずのコードがいつの間にかコピペされてゐること。ライブラリの中に實裝したはずのモジュールが使はれてをらず、その劣化コピーのやうなものが増殖してゐること。私だけが正しい事をしてゐて他の皆が間違ってゐるなどと言ふつもりは無い。私だって同じ程度には力量が足りない。ただ、正しいかどうか以前の問題として、酸素が足りないし、水も食糧も足りない。努力して補給しなければ生きられない。

 Haskell の純粋さ。プログラムを動的生成するプログラムを書く爲の本當に優れた方法である Monad と、それを一般化した Arrow。多相關數を部分適用してから、更にそれを他の關數と結合すること。スレッド間共有變數の參照と變更をログに殘して置き、樂観ロックに失敗したら自動的にロールバックすること。全ての要素が妖精のやうに身輕であり、肥後の守のやうに實用的であり、手を触れるとひんやり冷たい。

_ viernes, 16 febrero 2007 I/O しない部分に親切にしてやること
Cuenta Larga = 12.19.14.1.5; tzolkin = 1 Chicchan; haab = 18 Pax [Trackback Ping]

 自分の (あづか) り知らぬ所で環境が勝手に變化すると、物事が複雑になる事はあっても單純にはならない。プログラム言語の最初期の段階に於ては、變數と言へばグローバル變數しか無かった。そこに Lisp がローカル變數を導入して、後に(多分 Smalltalk が)インスタンスプライベート變數を導入した。ローカル變數の目的は勿論、自分が計算に使ってゐる變數の内容がプログラムの他の部分の影響を受けないようにする事だし、インスタンスプライベート變數は他所に送った struct の中身を勝手に變へられないやうにする爲のものだ。それならば、變數の値を變更すると云ふ行爲そのものを規制するのも然程をかしな事ではなからう。實際 C++ の const 修飾子は良い線を行ってゐた。const 修飾された變數の値を變へようとするとコンパイルエラーになったし、const 修飾されたメソッドが自分のインスタンス變數の値を變へやうとするとコンパイルエラーになった。ただ惜しい事に const 修飾の無い部分についてはそこが副作用を起こし得るかどうかが不明だった。さうしなければ C のスーパーセットでなくなってしまふ。

 プログラムが本質的に副作用を避けられない場合と云ふものは、實はプログラムの外部との通信を行ふ場合しか無い。つまり自分を取り卷く環境から影響を受けたり環境に對して影響を與へる場合の事だが、それは例へばファイルを讀む事だったり、ソケットを開く事だったり、別のスレッドからも見えるメモリ上の場所を書き換へる事だったりする。その他の副作用は本質的には必然でなく、例へば整數の配列の中から値がゼロであるやうな要素を數へる時に普通は次のやうに書くかも知れないが、

unsigned count_zero_ordinarily(const int* vec, const unsigned len) {
unsigned i;
int ret = 0;

for (i = 0; i <= len; i++) {
if (vec[i] == 0) {
ret++;
}
}

return ret;
}
 かう書く事も可能だ。
unsigned count_zero_recursively(const int* vec, const unsigned len) {
if (len == 0) {
return 0;
}
else {
return (vec[0] == 0 ? 1 : 0)
+ count_zero_recursively(vec + 1, len - 1);
}
}
 後者の素敵な所は勿論、副作用が一切起こらない事だ。ループカウンタ i は存在しないし、中間結果を保持する ret も存在しない。それらは再歸される關數呼出しと關數呼出しの狭間に隠されてゐて表に出て來ない。後者の缺點は動作速度で前者に負ける事だが、それもこんな風に末尾再歸に變へてコンパイラに最適化させれば解決可能である。
unsigned count_zero_tail_recursively_aux(
const int* vec, const unsigned len, const unsigned result) {

if (len == 0) {
return result;
}
else {
return count_zero_tail_recursively_aux(
vec + 1, len - 1, result + (vec[0] == 0 ? 1 : 0)
);
}
}

unsigned count_zero_tail_recursively(const int* vec, const unsigned len) {
return count_zero_tail_recursively_aux(vec, len, 0);
}
 本質的な副作用と云ふものはそんなに多いだらうか。大抵のプログラムはファイルを讀み書きする事が主目的ではなく、從ってプログラム全體の中でファイルの讀み書きそのものが占めるコードの量はそんなに多くはない。さう云ふものは例へばカーネルとかドライバとかに限られるのではないか。カーネルは C で書けば宜しい。それが最も適切だらう。でもアプリケーションを書く爲の言語はもっと I/O しない部分に親切にしてやっても (ばち) は當たらない。そこで Haskell の出番だと云ふわけだ。

 良く『Haskell には副作用が無いから素晴しい』などと言はれるが、副作用の無い言語でコードを書いた事の無い人間にこれを言っても理解される筈が無いだらう。私にしても同じ事で、そんなものは Prolog と同じで現實には役に立たないだらうと思ってゐた。結局それは間違った思ひ込みだった。

でも I/O は大切だ。それを冷遇してはいけない

 勿論 I/O は本當に大切であり、實世界に於けるプログラムを I/O 抜きに語る事は出來ない。そんな事をすれば Prolog になってしまふ。或る概念が冷遇されてゐるかどうかを調べるのに最も良い方法は、その概念を實際に表現した時に、その部分がコード内に美しく自然に溶け込んでゐるかどうかを見る事だ。例へば、整數を二つ取る關數 add に引數を一つだけ部分適用して、その結果を別の關數 display に渡す事を考へる。display は受け取った關數に別の整數を適用し、結果を出力する。これを C++ で書くとこんな風になる。
#include <iostream>
#include <boost/function.hpp>
#include <boost/bind.hpp>
using namespace std;
using namespace boost;

static int add(int a, int b);
static void display(const function<int (int)>& f);

static int add(int a, int b) {
return a + b;
}

static void display(const function<int (int)>& f) {
cout << f(100) << endl;
}

int main() {
display(bind(&add, 50, _1));
return 0;
}
 關數ポインタの代はりに const function<int (int)>& を使って、關數呼出しの代はりに bind(&add, 50, _1) を使ふのだ。普通に add を呼ぶなら add(50, 100) なのに。boost は實に上手くやってゐる。魔法のやうに上手くやってゐるのだが、それが立ってゐる足場はもう boost::bind を冷遇してゐるどころではなくて、そもそも C++ が設計された當初に式テンプレートと關數ポインタと匿名名前空間とその他の色々のものが複雜に組み合はさった魔術で關數の部分適用なんかをやってのける變人が現れる事が想像されてゐただらうか。その變人にしても物理法則までは變へられなかった。だから boost::bind では多重定義された關數に對する部分適用は出來ない。不便な事だが、そのやうな關數へのポインタを取る方法が無いからだ。この問題の所爲で上手く行かなくなるのは部分適用だけではなく、關數呼出しを遲延させるやうな全ての動作にとって制約になる。例へば boost::lambda とか。

 ところで同じコードを Haskell で書くとこんな感じになる。あまりに自然に溶け込み過ぎて一體何處で部分適用が行はれたのか判らない程だ。
add :: Int -> Int -> Int
add a b = a + b

display :: (Int -> Int) -> IO ()
display f = putStrLn $ show $ f 100

main :: IO ()
main = display $ add 50
 次は I/O の例を擧げる。標準入力から一行ずつ讀んで、引數で與へられた單語を含んでゐる行だけを抜き出して、それらを行番號付きで標準出力に吐くやうな僞 grep を考へる。このプログラムが本質的に副作用を必要とするのは (1) プログラム引數の參照 (2) 標準入力からの讀込み (3) 標準出力への書き込みの三つだけだ。これは例へばこんな風に書ける。
import Control.Monad
import Data.List
import System.Environment

main :: IO ()
main = do word:[] <- getArgs
input <- getContents
mapM_ (putStrLn . format) $ (grep word . lines) input
where
format :: (Integer, String) -> String
format (num, line) = show num ++ ": " ++ line

grep :: String -> [String] -> [(Integer, String)]
grep word xs
= filter (\ (_, line)
-> line `contains` word) $ zip [1..] xs

contains :: String -> String -> Bool
contains world word
= any (\ tail
-> word `isPrefixOf` tail) $ tails world
 I/O してゐるのは main の定義の先頭三行だけだ。この三行は他の部分から浮いて見えるだらうか。私にはさう見えない。

_ lunes, 12 febrero 2007
Cuenta Larga = 12.19.14.1.1; tzolkin = 10 Imix; haab = 14 Pax [Trackback Ping]

No  Points     Name                                                   Hp [max]
1 4444626 airscope-Sam-Hum-Fem-Law ascended to
demigoddess-hood. 177 [177]
 二度目の昇天。HP は270くらゐあったのだが Astral Plane で Death に削られた。以下反省點。

・未鑑定の scroll of amnesia を讀んでしまった。對策 → 賣価200付近の未鑑定の卷物は讀まない。このクラスの卷物は create monster, taming, amnesia, earth の四種類しか無く、earth は倉庫番に行けば確實に判定可能なので、讀んで鑑定する必要は全く無い。

・scroll of destroy armor が未鑑定だった事を忘れてゐた所爲で、序盤で拾った speed boots が壊れた。對策 → 記憶に頼らない。鑑定したかどうかをメモして置くべき。

・mind flayer に腦を二囘喰はれた。こいつに遭遇した時は scroll of genocide が未鑑定だったのでつい戰ってしまったが、後でベースキャンプに戻ったら未鑑定の卷物二種類のうち片方が genocide だった。對策 → 他に何の手段も無くなるまでは絶對に戰ふべきでない。特に potion of paralysis を惜しむな。

・Wizard of Yendor の塔の三階から降りる時、Burdened のまま cockatrice corpse を手に持って梯子を降りた。amulet of life saving が一つ無駄に。對策 → Burdened の時に c を持つな。荷物を置いてからにすべき。

・未鑑定だった呪はれた靴が實は speed boots だった。これに氣付いたのは Gehenom のかなり奥まで行ってからだった。對策 → 呪はれてゐようが何だらうが未鑑定の靴を放置するのは良くない。

・何度も水に落ちた。4、5囘は落ちた。對策 → 面倒でも水場を歩く時は必ず ring of levitation か water walking boots を裝備する。

・Orcus フロアで bag of holding が呪はれた時、Overtaxed になったのでうっかり地面に置いてしまったのだが、その時 wand of cancellation を持ってゐなかった。holy water はあったが持ち上がらないのでどうしようも無い。cancellation を置いてあるベースキャンプは Medusa レベルの上だったし、ring of levitation も water walking boots も bag of holding の中。wand of wishing も鞄の中だ。何とか歸らなくてはと思って wand of cold で城の堀を凍らせたが Medusa 島から階段へ行くまでの間に空になった。万策尽きたと思ったが uncursed magic lamp を持ってゐる事を思ひ出したので、それを blessed にして見事 cancellation を願へた。對策 → Medusa 島から下へ行く時には cancellation を忘れるな。あと bag of holding を uncursed にされたらすぐに氣付いて blessed に戻せ。

_ viernes, 9 febrero 2007
Cuenta Larga = 12.19.14.0.18; tzolkin = 7 Etznab; haab = 11 Pax [Trackback Ping]

 田口ランディさんのブログにコメント欄が無いのを發見して、なぜ私がこれまでずっと『ブログへのコメント』と云ふものに漠然とした不自然さを感じてゐたのか判った氣がする。

 私は自分の日記(それとも RSS と TB があるからブログ?)で特定の人間に向かって話をする事は滅多に無くて、殆どいつも不特定の人間に向けて書いてゐる。私だけでなく普通さうだらう。そこへ偶然訪れた特定の人がその記事のすぐ下にコメントを書くと、ブログを書いた側は nobody に向かって話してゐるのにコメントを書いた側は個人として相手に話す事になってしまふ。ここに捻れがある。コメントを付ける時の名前欄に「通りすがり」と記入するかどうかとは關係ない。名を伏せようとどうしようと個人がネットに於ける nobody を代表する事は出來ない。

 この捻れを柔らげるには、ブログを書く側とコメントを付ける側の距離をもう少し離してやれば良いのではないか。個人も距離を置けば nobody に取り込まれ、nobody の一部になる。私がこの日記システム、libroverde.cgi を書いた時に、コメント欄は意圖的に外したのに TB は實裝した理由はさう云ふ事だったのだらうと思ふ。TB は他人の領域から響いて來る (こえ) に過ぎず、生身で自分の庭にまで入って來るコメントとはかなり距離が違ふのだ。

_ martes, 6 febrero 2007 Haskell の forall キーワードについて
Cuenta Larga = 12.19.14.0.15; tzolkin = 4 Men; haab = 8 Pax [Trackback Ping]

 Haskell の forall キーワードは途轍もなく難しいし、簡單に讀める資料があまり無い事もあって良く分からなかったのだが、やうやく何とかその尻尾くらゐは掴んだやうに思ふので、その手を離して仕舞はない内に書いて置く。ここに書いてある内容は私の拙い理解に基いてのものであるから、完全に正しいとは確信してゐない。だから、この記事をお讀みになる方にはその點に注意して戴きたい。

型變數のスコープ

mkList :: a -> [a]
mkList x = [x]
 このやうな關數 mkList があるとする。(ここでは型のみが重要なので實裝はどうでも良い。)mkList の型は a -> [a] だが、この例で型變數 a は型全體の中で參照されてをり、型全體が a のスコープになってゐる。と云ふ事はつまり型 a -> [a] が具體化される時には二箇所に現れてゐる a がどちらも同じ時點で具體化されるし、その時は必ず同じ型に具體化される事になる。この事を forall を使って明示的に書くと次のやうになる。forall を使はなかった前者の型は、實は後者の型の省略形に過ぎない。型變數 a は『ランク1の型』だ。
mkList :: forall a. a -> [a]
 forall を使ふ事で、特定の型變數のスコープを次のやうに變更する事が可能だ。この時の型變數 a は『ランク2の型』と呼ばれる。
foo :: (forall a. a -> [a]) -> [Int]
foo f = f 100

引數に多相關數を取る關數

 この關數 foo は、型が a -> [a] であるやうな多相型の關數を多相關數のままで取って引數 100 を渡す。渡された關數が自分の引數に對してどのやうな演算をするかはその關數次第だが、自分に與へられた引數の型を變更せずにそれをリストの中に入れて返す事だけは約束してゐる。だから例へば foo を次のやうに呼び出すと、このプログラムは "[100]" と表示する。
main :: IO ()
main = print $ foo mkList
 一方で、次のやうな呼出しはコンパイルエラーになる。foo は多相關數 a -> [a] を取る事になってゐるのに、mkStrList は具體的に過ぎるからだ。
mkStrList :: String -> [String]
mkStrList str = [str ++ "!"]
main :: IO ()
main = print $ foo mkStrList -- エラー!

普通の關數

 もし foo の型が次のやうになってゐた場合はどうだらうか。
foo' :: (a -> [a]) -> [Int]
 foo' の引數には forall が無いので、foo' の取る引數はもはや多相關數ではなく、foo' の呼出側によって具體化された關數として渡されるやうになる。だから例へば次のやうな呼出しは正當なものになる。foo' の引數として String -> [String] を渡してゐる。
main :: IO ()
main = print $ foo' mkStrList
 では、foo' の方では引數として受け取った關數をどう使へば良いだらうか。受け取ったのは多相關數ではないから、關數 a -> [a] の實際の型を決める權利は foo' には無い。だから次のやうな實裝はコンパイルエラーだ。
foo' :: (a -> [a]) -> [Int]
foo' f = f 100 -- エラー!
 f は foo' にとっては何だか分からない型 a の引數を慾しがってゐるのだが、foo' の型が例へば (a -> [a]) -> a -> [Int] のやうなものでない以上その型の値は手に入らないので、結局渡された f を呼ぶ手段は無い事になる。例へばこんな風に。
foo' :: (a -> [a]) -> [Int]
foo' f = [123] -- f を呼ばないから問題無い

 以上が現在の私の理解に於ける rank-2 polymorphism(ランク2多相性)である。forall の使ひ方には他にも extentially quantified type(存在量化された型)や rank-N polymorphism(ランクN多相性)がある。前者は下にある WikiBooks の記事を讀めば簡單に分かるが、問題は後者だ。ランク2多相性を拡張した概念のやうだが、まだ私は理解してゐない。

で、これを何に使ふのか?

 GHC のソースを grep してみたら、GHC の内部の libraries/base/GHC/Base.lhs で使はれてゐた。どうもコンパイルの過程で使はれるやうな感じだ。…つまり、どう使へば良いのか良く分かりません。

資料

Haskell/Extentially quantified types
7.4.8. Arbitrary-rank polymorphism

_ lunes, 5 febrero 2007 Module::Install::Philosophy 日本語譯
Cuenta Larga = 12.19.14.0.14; tzolkin = 3 Ix; haab = 7 Pax [Trackback Ping]

 半年か一年くらゐ前に和譯して置きながら、その飜譯が全然滿足の行くものにならなかった爲にお蔵入りにしてあった Module::Install::Philosophy。最近急にそれを思ひ出したので、折角のこの機會に飜譯を最初からやり直した。

 Philosophy-ja.html

 今囘はそんなにまずくない出來になってゐると思ふ。でも原文を讀める人はそっちを讀んだ方がいいでせう。

 私はもう私用で perl のコードを書く事は殆ど無いだらうし、あったとしても既に perl で書いてしまったものを少し改良したりバグを潰したりする程度なので、CPAN の抱へてゐた社會的な問題については實はそんなに興味があるわけではない。ただ私がこれを和譯する氣にまでなったのは『メンテナンスとデジタル腐敗』の所爲。この問題はかなり長い間、私の惱みの種であって、今でも別に何とかなったわけではない。私は Ingy döt Net が感じてゐるのと同じ罪惡感を感ぜずに濟むやうに、そこから上手く逃がれられるやうに、何とか努力してゐるだけだ。つまり自分にかう言ひ聞かせるわけだ。『興味が失せたのは仕方無いぢゃないか。確かに改良を期待してゐるユーザーは居るかも知れないが、私には時間も氣力も無限にあるわけではないし、過去に書いたコードを放置するのは別に仁義に (もと) る事ではない。そもそも私が書かなければ最初から何も無かったわけだし、世の中には作者が飽きた所爲で何年も放置されてゐるプロジェクトは幾らでもある。第一、意慾に滿ちてもゐないのにどうして良いコードを書けるものか』と。理屈ではないのだ。かう云ふ感覺は。だから幾ら理屈で抑へ込もうとしても上手く行きはしないのだが、他の方法を知ってゐるわけでもない。知ってゐるくらゐならば惱みもしない。

 全く不思議な事なのだが、これと同じ事を Ingy 以外の人物が言ってゐるのを一度も聞いた事が無いのは何故だらう。この問題について表明するのは一種のタブーになってゐる?まさかそんな事はあるまい。本當にタブーならば必ず誰かがそのタブーに触れて、必ず他の者がそれを血走った目で怒鳴り付けるはずだからだ。では、誰もそんな罪惡感は感じてゐない?私と Ingy だけがをかしいのか?それも考へ難い。

_ domingo, 4 febrero 2007 Selfward English Speaking
Cuenta Larga = 12.19.14.0.13; tzolkin = 2 Ben; haab = 6 Pax [Trackback Ping]

 In these days I am bilingual in my mind. For instance, when I feel "彼と話をするといつも酷く疲れる" in Japanese, I feel "He makes me exhausted whenever I talk with him" after the Japanese feeling. (For those who don't speak Japanese, these two sentences have basically an identical meaning.) A similar thing occured when I was practicing touch typing: whenever I felt something, I kept typing the sentence I felt using my imaginary keyboard. That was totally uncontrollable as I couldn't prevent myself from repeating that stupid activity even when what I felt was too serious to allow any humors.

 English is indeed a convenient language to communicate with many people in the earth, but it is neither sophisticated nor linguistically interesting. Especially, the pronouncing system is inconsistent. Is there any good reason to pronounce the word "weird" as wi-ard while no other words that have "ei" in them are pronounced like that? How about the difference between deal and death? How about touch and tout? Such weirdness hardly occurs in any other languages in the earth as far as I know.

 English idioms are also weird that they can't even be easily found as idioms. How can I find e.g. "make it" an idiom if I haven't known that it's an idiom previously? How about "if need be"? The latter is somewhat better because it is syntactically illegal so recognizing it an idiom is easier than the former. "Make it" is abysmal. It always confuses me.

 But well, my favorite part of English is that it has plenty of straightforward negative words than Japanese. Wretched, unpleasant, stupid, ugly, foul, bogus, wacky, bloody, awful, offensive, pesty, tiresome, nasty or hideous! It is amusing to choose from those words to express my sentiment, because it is not easy to say "You mother fucker!" in Japanese.

_ viernes, 2 febrero 2007 日常的使ひ捨てスクリプト書き
Cuenta Larga = 12.19.14.0.11; tzolkin = 13 Chuen; haab = 4 Pax [Trackback Ping]

 中規模以上のソフトウェア、例へば IRC bot や BitTorrent 互換クライアントを所謂 Lightweight Language で書かうとするのは面白い試みではあるが、面白いのは書き始めてからその最初のバージョンが完成するまでの間だけだ。それを一度 (ひとたび) 完成させて「嗚呼やれやれ」とサイトに置き、一年程それを放っておいた後、禍々しくも ToDo リストに溜まってゐたバグ修正や要改良點の山をやうやく片付ける氣になった時――直しても直しても實行時エラーを吐き續けるプログラムを相手に必ず苦戰する事になる。それも實行時エラーになるならまだ良い方で、『0以上の數値が表示されるべき部分に空白文字が表示されてゐる』とか、『一見正常に動いてゐるのに生成されるはずのファイルが生成されない』などと云った厄介な不具合として現れる事も多い。

 要するに或る變數や關數が存在するかどうかが實行時まで分からなかったり、關數の取る引數の數が實行時まで分からなかったり、そもそも變數が型情報を持ってゐなかったりするからプログラム全體の整合性を保つのにプログラマ自身が苦勞しなければならないわけで、コンパイル時にこれらのエラーを檢出出來るのであれば實行時エラーが起こる機會を大幅に減らす事が出來る。勿論この利益はタダで得られるわけではなく、その代はりにソースファイルに書く必要のある文字數が Lightweight Language で書いた場合と比べて大きく増えるから、その分だけ開發初期に必要な時間も長く掛かるやうになる。將來ソースを弄くる時の爲の先行投資だ。

 しかし一度書けば二度と弄らないやうな使ひ捨て(或いは『書き放置』)スクリプトの場合には、このやうな先行投資は必要無く、單に無駄になる。例へば非常に良くある課題―― cdparanoia -Bw はドライブに插入されてゐる音樂 CD の各トラックを慎重に慎重に讀み取りながら、現在のディレクトリに "track01.cdda.wav", "track02.cdda.wav", ... のやうなファイルを作成する。私はこのファイル名規則が氣に入らないので、リッピングが終はった後でも良いから "01.wav", "02.wav", ... のやうなファイル名に變更したい。勿論手動で mv track01.cdda.wav 01.wav などとタイプするのは眞っ平であって、ここは當然スクリプトを書くべき所である。それも出來れば五分で。

 ではどの言語で書くのが最も適切か。

sh

#!/bin/sh

for fname in *; do
new_fname=`echo "$fname" | sed -e 's/^track\([0-9]\+\)\.cdda\.wav$/\1.wav/'`

if [ "$fname" != "$new_fname" ]; then
echo "$fname -> $new_fname"
mv "$fname" "$new_fname"
fi
done
 確かに書けはするが最低だ。new_fname=`echo... の (くだり) などまるで食事用のナイフで鉛筆を削らうとしてゐるかのやうだ。

perl
#!/usr/bin/perl
opendir my $dh, '.';

while (defined ($_ = readdir $dh)) {
if (/^track(\d+)\.cdda\.wav/) {
print "$_ -> $1.wav\n";
rename $_ => "$1.wav";
}
}
良い所:
 - 短くて簡潔。
惡い所:
 - 見た目が美しくないし、讀み易くもない。
 - エラー處理が全然まともに行はれてゐない。

 二番目の缺點は特に問題だと思ふ。例外の概念が後になってから追加された爲か、perl の組込み關數はエラー發生時に例外を投げる代はりに false 値(perl では undef や 0 等)を返したりする。ロードすると組込み關數を置き換へてエラー時に例外を投げるやうにするモジュールもあったやうに思ふが、デフォルトの状態はまるで C の關數のやうだ。エラー處理を行はうとするならば、最初の opendir は『opendir my $dh, '.' or die "カレントディレクトリを開けません: $!";』などと書かなければならない。

Scheme
#!/usr/local/bin/csi -b -q
(use posix
regex)

(for-each (lambda (fname)
(match (string-match "track(\\d+)\\.cdda\\.wav" fname)
[#f #f]
[(_ num) (printf "~a -> ~a.wav~%" fname num)
(rename-file fname (string-append num ".wav"))]))
(directory))
 處理系は Chicken

良い所:
 - 行數は少ないし、見た目も惡くない。
惡い所:
 - 正規表現がバックスラッシュだらけで欝陶しい(但しGaucheには正規表現リテラルがある)。
 - 關數名が無駄に長い。

 どうして文字列を結合する關數に string-append なんて名前を付けるのか。確かに (++ num ".wav") とか (concat num ".wav") などと違って予備知識が無くても讀めるのはメリットかも知れないが、そんな事の爲に毎囘 string-append とタイプするのは卒直に言って面倒だ。

Haskell(その一)
#!/usr/local/bin/runhaskell
import System.Directory
import Text.Regex

main :: IO ()
main = do files <- getDirectoryContents "."
mapM_ fixIfNeedBe files

fixIfNeedBe :: FilePath -> IO ()
fixIfNeedBe fName
= case matchRegex (mkRegex "^track([0-9]+)\\.cdda\\.wav$") fName of
Nothing -> return ()
Just [num] -> do putStrLn (fName ++ " -> " ++ num ++ ".wav")
renameFile fName (num ++ ".wav")
良い所:
 - 讀み易い。
 - 見た目が美しい。
惡い所:
 - 長い!
 - やっぱり正規表現がバックスラッシュだらけ。
 - putStrLn の引數を構築する部分が ++ だらけ。
 - そもそも正規表現を使ふのは Haskell らしくない。

 Haskell はあまり Lightweight でないので、頭を使はずに書く事が出來ない。このコードを五分で書くのは難しい。

Haskell(その二)
#!/usr/local/bin/runhaskell
import System.Directory
import Text.ParserCombinators.Parsec

main :: IO ()
main = do files <- getDirectoryContents "."
mapM_ fixIfNeedBe files

fixIfNeedBe :: FilePath -> IO ()
fixIfNeedBe fName
= case parse paranoia "" fName of
Left _ -> return ()
Right num -> do let newFileName = num ++ ".wav"
putStrLn (fName ++ " -> " ++ newFileName)
renameFile fName newFileName

paranoia :: Parser String
paranoia = do string "track"
num <- many digit
string ".cdda.wav"
return num
良い所:
 - 書くのが非常に樂しい。
 - 見た目がとても美しく、動作内容も美しい。
惡い所:
 - 更に長い!
 - Parsec の使ひ方なんて暗記してないので、とても五分で書けない。モジュールの正式名稱さへ覺えてゐない。

Python
#!/sw/bin/python
import re
import os

for fname in os.listdir("."):
m = re.match(r'track(\d+)\.cdda\.wav', fname)
if m:
print '%s -> %s.wav' % (fname, m.group(1))
os.rename(fname, m.group(1) + '.wav')
良い所:
 - 行数が少ない。
 - 讀み易い。
悪い所:
 - MatchObject オブジェクトの存在が冗長

 一般に、或る處理の結果として種類の異なる複数の値が計算される場合には、計算後の値が返されるパターンには次のものがある。

 1. 單一のリストまたは配列が返される
 Chicken(Scheme)の string-match や Haskell の matchRegex、JavaScript の RegExp#exec などがこのパターン。リストや配列へのアクセスは文法的に短くされてゐる場合が多い上(m[1]等)、さうでなくても進んだ言語であれば受け取り側でパターンマッチングと變數束縛、それに基いた分岐を行ふ事が出來る。後者の場合は返された値のそれぞれについて必要な部分にだけ變數名を割り當てる事が出來て大變便利である。

 但し、正規表現の部分マッチのやうに返される値が本質的にリストである場合は良いのだが、さうではなく元々順序關係の無い値をリストの各位置に人爲的に並べる方法は最惡だ。これは特に perl の built-in 關數に多い病氣で、例へば stat($fname) 關數の戻り値は次のやうなリストである。これは餘りにも酷いのでリストの代はりにオブジェクトが返されるやうになるモジュール(File::stat)も作られたが、一體どのやうな人間の腦がこの順序を記憶してゐられるだらうか。
 (デバイス番號, inode 番號, モード(種類とパーミッション), ハードリンクの數, uid, gid, デバイス識別子, 大きさ, 最終アクセス時刻, 最終更新時刻, 最終ファイル状態變更時刻, ブロックの大きさ, ブロックの數)

 2. 特殊變數に格納される
 Perl や Ruby の =~ 演算子がこのパターン。特殊變數は無限に増やすわけには行かないのでマッチング結果や errno 等の場合にしか使へず、更に束縛したい變數名を自分で指定出來ずに常に固定される事から來る獨特の讀み難さがあるが、コードは短く出來る。

 3. 多値として複數の値が直接返される
 Scheme や Common Lisp。そもそも多値と云ふ概念を持つ言語は數少なく、私はこの二つ以外に知らない。使ひ方としては 1 の單一リストの場合とほぼ同樣。

 4. 單一のオブジェクトが返される
 Python の re.match や Java の java.util.regex パッケージがこのパターン。常に成功する計算ならばこのパターンでもまあ良いのだが、失敗するかも知れない計算の場合は (1) 結果オブジェクトに變數を束縛する (2) そのオブジェクトを見て計算が成功したかどうかを確認し、(3) 成功してゐるならばそのメソッドを呼びながら目的の處理を行ふ、と云ふ三段階を手動で踏む必要があって、これはパターンマッチのある言語から見れば大いなる徒勞だ。何しろこの三つの行程は全てたった一度のパターンマッチで記述可能なのである。

Ruby
#!/sw/bin/ruby

Dir.entries(".").each do |fName|
if /^track(\d+)\.cdda\.wav$/ =~ fName then
print "#{fName} -> #{$1}.wav\n"
File.rename(fName, $1 + ".wav")
end
end
良い所:
 - 簡潔であり、動作が美しい。
 - 見た目も餘り惡くない。

 惡い所は特に無いやうな氣がする。美しさでは Haskell(その二)に負けるが、そもそもこのコードは五分で書き終えたら二度と讀むつもりも無いので、美しさは別に重要ではないのでした。

結論
 使ひ捨てスクリプトは Ruby で書くと良い。

2002
   10 11 12
2003
   1 2 3 4 5 6 7 8 9 10 11 12
2004
   1 2 3 4 5 6 7 8 9 10 11 12
2005
   1 2 3 4 5 6 7 8 9 10 11 12
2006
   1 2 3 4 6 7 8 9 10 11 12
2007
   1 2 3 4 5 6 7 8 9 10 12
2008
   1 4

login