transcoded-port
等の手続きでバイナリポートを文字列ポートに変換する。ここまでは特に問題ないだろう。さて、ここからが問題である。以下のコードは何を出力するだろうか?
(display "\xFF;\xD8;")期待する挙動としては
"\xFF;\xD8;"
がそのまま出力される、つまり0xFF 0xD8
として2バイト出力されることを期待するだろうか?上記の挙動を期待した人は手をあげなさい。
(・ω・)ノシ
手を上げた人のそのまま残りなさい、補習があります。挙げなかった人はこのまま帰ってもよいです。もちろん補習を受けてもよいですよ。
さて、実際の挙動を見てみよう。大抵の処理系では出力される文字列は
"ÿØ"
となり、これのバイナリ表現はUTF-8で0xC3 0xBF 0xC3 0x98
になるだろう。勘がいい方は気づいたかもしれないが、この挙動の正体はトランスコーダの仕業である。display
に出力ポートを指定しなかった場合current-output-port
が使われるのは周知のことであると思われる。current-output-port
にはどんな文字列ポートが割り当てられているのだろうか?R6RSによると以下である。These return default textual ports for regular output and error output. Normally, these default ports are associated with standard output, and standard error, respectively. (omit) A port returned by one of these procedures may or may not have an associated transcoder; if it does, the transcoder is implementation-dependent.処理系依存である。例えばSagittariusでは
要約:規定の出力ポート。通常は標準出力に割り当てられる。返されるポートにはトランスコーダが紐つけられているかもしれない。もしそうならそれは処理系依存である
(native-transcoder)
が割り当てられるし、Chezはトランスコーダを割り当てていない。処理系依存の挙動ではいまいち納得が行かないので、これを処理系非依存の挙動で書いてみる。
(call-with-bytevector-output-port (lambda (out) (put-string out "\xFF;\xD8;")) (make-transcoder (utf-8-codec)))このコードを実行すると上記と同様の出力が得られる。文字
#\xFF
が#\ÿ
なるのかというのはUCS4とUTF-8の変換表を見てもらいたい。ではどうすれば変換せずに出力できるのか?答えは割と簡単で
latin-1-codec
を使うと良い。上記のコードを以下のように書き直すと予定通りに動く:
(call-with-bytevector-output-port (lambda (out) (put-string out "\xFF;\xD8;")) (make-transcoder (latin-1-codec)))もちろん、文字列の中に多バイト文字が混じっていた場合はエラーになるので気をつける必要があるが。
余談ではあるが、この問題を回避するポータブルな方法はR7RSにはない。つまり、文字列をバイナリ表現として扱う非処理系依存なコードは現状のR7RSでは書けないということになる。また、少なくともChibiとGaucheでは文字列
"\xFF;\xD8;"
をdisplay
で出力したら"ÿØ"
が出力された。R7RS-largeに期待したい類の問題である。