* まもなく移轉
當サイトはまもなく移轉します。
* みっくみく事件の決着
驚いた。ドワンゴが殆ど完全に折れる形で、しかもこんなに早く決着が付くとは思はなかった。こんな結末になるとは夢にも思はなかった。
* Quốc ngữ と日本語
ベトナム語は六つの聲調を持つ言語であるが、現在それを表記する爲に用ゐられてゐる Quốc ngữ ではその六声調を書き分ける事が出來るらしい。
* 自然物と信仰/生體機械としての人間/初音ミク/オープンソース戰爭
みっくみくが JASRAC された件には非常にもやもやとさせられる。だから出來るだけ消化しようと試みた。Every man thinketh his burden is the heaviest.
* HsHyperEstraier 0.1
HyperEstraier の Haskell 用バインディングである HsHyperEstraier を公開した。
_
viernes, 2 febrero 2007
日常的使ひ捨てスクリプト書き
Cuenta Larga = 12.19.14.0.11; tzolkin = 13 Chuen; haab = 4 Pax
[Trackback Ping]
中規模以上のソフトウェア、例へば IRC bot や BitTorrent 互換クライアントを所謂 Lightweight Language で書かうとするのは面白い試みではあるが、面白いのは書き始めてからその最初のバージョンが完成するまでの間だけだ。それを
要するに或る變數や關數が存在するかどうかが實行時まで分からなかったり、關數の取る引數の數が實行時まで分からなかったり、そもそも變數が型情報を持ってゐなかったりするからプログラム全體の整合性を保つのにプログラマ自身が苦勞しなければならないわけで、コンパイル時にこれらのエラーを檢出出來るのであれば實行時エラーが起こる機會を大幅に減らす事が出來る。勿論この利益はタダで得られるわけではなく、その代はりにソースファイルに書く必要のある文字數が Lightweight Language で書いた場合と比べて大きく増えるから、その分だけ開發初期に必要な時間も長く掛かるやうになる。將來ソースを弄くる時の爲の先行投資だ。
しかし一度書けば二度と弄らないやうな使ひ捨て(或いは『書き放置』)スクリプトの場合には、このやうな先行投資は必要無く、單に無駄になる。例へば非常に良くある課題―― cdparanoia -Bw はドライブに插入されてゐる音樂 CD の各トラックを慎重に慎重に讀み取りながら、現在のディレクトリに "track01.cdda.wav", "track02.cdda.wav", ... のやうなファイルを作成する。私はこのファイル名規則が氣に入らないので、リッピングが終はった後でも良いから "01.wav", "02.wav", ... のやうなファイル名に變更したい。勿論手動で mv track01.cdda.wav 01.wav などとタイプするのは眞っ平であって、ここは當然スクリプトを書くべき所である。それも出來れば五分で。
ではどの言語で書くのが最も適切か。
sh#!/bin/sh
確かに書けはするが最低だ。new_fname=`echo... の
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
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
處理系は Chicken。
(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))
良い所:
- 行數は少ないし、見た目も惡くない。
惡い所:
- 正規表現がバックスラッシュだらけで欝陶しい(但し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