Duck TypingとFILE構造体に思うこと

組込み関連の仕事をしていることもあり、納品用に書いているコードはC言語C++なのだが、納品物以外は別の言語で書くことが多い。テキスト処理にはsedgawkを使うことが多いが、それ以外にもRubyWSH + JScriptを使ったりする。

id:eel3:20080810 でも書いたが、小ツールを実装する時は標準入力とファイルのどちらからの入力にも対応できるようにしている。Rubyの場合、標準入力は組込み定数の STDINで、ファイルはFileクラスのオブジェクトだ。STDINはIOクラスに属していて、FileクラスはIOクラスを継承しているので、どちらも大体同じように扱うことができる。

if ARGV.size == 0
  file = STDIN
else
  begin
    file = open(ARGV[0], "r")
  rescue
    print "Cannot open file: ", ARGV[0], "\n"
    exit(2)
  end
end

begin
  file.each_line do |line|
    print line
  end
rescue => e
  print "error raised: ", e, "\n"
else
  # EMPTY
ensure
  file.close
end

WSH + JScriptの場合、標準入力はStdInオブジェクトで、ファイル(というかファイルへの入出力)はTextStreamオブジェクトだ。両者の関係は不明だが、まったく同名のプロパティとメソッドを持っているので、同じように扱うことができる。

// var stderr = WScript.StdErr;
// var stdin  = WScript.StdIn;
// var stdout = WScript.StdOut;
// var argv0  = WScript.ScriptName;
// var args   = WScript.Arguments;

var file = null;
if (args.length == 0) {
	file = stdin;
} else {
	var filesystem = WScript.CreateObject("Scripting.FileSystemObject");
	try {
		file = filesystem.OpenTextFile(args(0));
	} catch (e) {
		stderr.WriteLine(argv0 + ": " + args(0) + ": open failed");
		return 2;
	}
}

while (!file.AtEndOfStream) {
	stdout.WriteLine(file.ReadLine());
}

file.Close();

動的型付け言語の場合、例え標準入力とファイル入力が継承関係のないオブジェクトで表現されていたとしても、両者が同名のプロパティやメソッドを持っているのなら、ソース上では同じように扱った実装をすることができる。こういうときDuck Typingって便利だよね、という話(蛇足ながら、C++STLとかも好き)。

ところで、面白いことに標準入出力とファイル入出力に関しては、静的型付け言語であるC言語でも両者を区別することなく扱うことができる。どちらもソース上ではFILE構造体へのポインタで、同じ関数を使えるからだ。Unix環境では更に標準入出力もファイル入出力もソケットもファイルディスクリプタで表現可能で、同じ関数で扱うことができる。

Tclの入出力まわりもUnixに近い。入出力はチャネル識別子という形で抽象化されていて、その中では標準入出力もファイルもパイプラインも同一に扱えるようになっている。

set input_from_stdin false
if {$argc == 0} {
	set file stdin
	set input_from_stdin true
} else {
	set argv1 [lindex $argv 0]
	if {[catch {set file [open $argv1 r]}]} {
		puts stderr "Cannot open file: $argv1"
		exit 2
	}
}

while {![eof $file]} {
	gets $file line
	puts $line
}

if {!$input_from_stdin} {
	close $file
}

これは私の妄想だが、少なくとも標準入出力とファイル入出力に関しては、Unix開発の比較的初期のどこかの段階で抽象化によって両者を区別せずに同じように扱えるようにするとメリットがあるという判断がなされたはずだ。Unixにパイプとリダイレクトが組み込まれて以降、その考え方はうまく機能していると思う。

もし標準入出力とファイル入出力が全く異なるものだったらと考えると、怖いものがある。C言語というかUnixの初期段階におけるデザイン選択のうまさを感じてしまうのだが、勘違いだろうか?