unicode は 16bit という誤解が未だに根強くあるようだ。「ユニコード戦記」でも小林龍生氏が「ユニコードの16ビット固定長神話」と一項をあげて説明しているのに、IT用語辞典は非常に誤解を招く書き方をしているし、Wikipedaでは「サロゲートペア(代用対)の方式は16ビット固定長を志向したUTF-16との互換性維持のために設けられた拡張であり、UTF-8やUTF-32では利用できない」とまで書いている。根強い神話の一つには確かに歴史的経緯では初期にそういう時があったということもあるが、もう一つにはユニコードとはUTF-8, UTF-16, UTF-32のことという、誤解というか区別が曖昧にしか理解していない、ということがあるように思われる。

ユニコードとは、「2バイトで表せる範囲(65,536)のコードの集まりを面(Plane)と呼んでいて、0から16までの全部で17の面から構成される」(ニコニコ大百科)文字の計算機表現のことであるが、これはいわば各々の文字一つをどう表現するかの抽象的モデルであって、それを実際にビット列でどう表現するかとは別の話である。書き物の表現としてユニコードを表現する時には、たとえば U+4E9C などと表現され、それはもし計算機にしかるべきフォントが用意してあって漢字表現できるのなら、正しくは「亜」と印刷されるはずであるが、計算機内部でどのように実装されるのかというのは、論理的にはまったく別の話なのである。同様に、「サロゲートペア(代用対)[は] UTF-8やUTF-32では利用できない」というのはコードポイントとエンコード表現を区別しない全く意味をなさない記述なのである。ただしくは、「ユニコードのサロゲートペアに相当する漢字はUTF-8, UTF-16, UTF-32のエンコード方法では、次のようなオクテットで表現される」なのである。このページを見てほしい。これでも本当はまだ不十分で、8bitバイト列(オクテット)でUTF-8, UTF-16, UTF-32が書いてあるが、これに実際のビット列としてはビッグエンディアン、リトルエンディアンの違いを考慮しなければならない。

さて、今後の話をはっきりさせるために、外部表現の入力、internalization、実装としてのオブジェクト、externalization、外部表現への出力、と言う話をしたい。次の図を見てほしい。

Model3




この図は 3Lisp で有名な Brian Smith の論文から採ったものであるが、Lisp の S式が Notation N1 で入力され、それが内部のオブジェクトの構造に反映され、Eval の結果が異なる内部構造になり、それが外部表現されて、Notation N2として印刷される。こんなことは、read-eval-print ループを持つ Lisp ではいうまでもないことだが、Lisp のように read-table で internalization を制御できたり、オブジェクトの print 関数でどんな外部表現にでも制御できるといった経験のないプログラマーには、なかなか理解できないことのように思われる。だから、計算機に対して「亜」をどのように外部表現して入力するのか、それが計算機内部でどう実装されているのか、それがどのように印刷されるのか、「亜」なのか「U+4E9C」なのかというのは、しっかり区別してほしいのだ。内部ではしっかりユニコードが表現されていても、それを外部表現するためのフォントが用意されてなければ、「亜」とは印刷されないし、OS にはあってもアプリが(言語が)externalization できなければやっぱり「亜」とは印刷されない。Lisp の話は次にするとして、Python 2.6.6 ではこんな風になる。
>>> foo = '亜'
>>> foo
'\xe4\xba\x9c'
>>> bar = u"\u4E9C"
>>> bar
u'\u4e9c'
>>> ord(foo)
Traceback (most recent call last):
File "", line 1, in
TypeError: ord() expected a character, but string of length 3 found
>>> ord(bar)
20124
>>> chr(20124)
Traceback (most recent call last):
File "", line 1, in
ValueError: chr() arg not in range(256)
sbcl ではこんな感じ。
* (setq foo #\亜)

; in: LAMBDA NIL
; (SETQ FOO #\U4E9C)
;
; caught WARNING:
; undefined variable: FOO
;
; compilation unit finished
; Undefined variable:
; FOO
; caught 1 WARNING condition

#\U4E9C
* foo

#\U4E9C
* (char-code foo)

20124
* (code-char 20124)

#\U4E9C

ちなみに、サロゲートペア以外については、Allegro CL の mlisp がもっとも理想的である。
seiji@agent:~$ acl90/mlisp
International Allegro CL Enterprise Edition
9.0 [64-bit Linux (x86-64) *SMP*] (May 27, 2013 19:37)
Copyright (C) 1985-2012, Franz Inc., Oakland, CA, USA. All Rights Reserved.

This development copy of Allegro CL is licensed to:
[TC20412] National Institute of Informatics (NII)

;; Optimization settings: safety 1, space 1, speed 1, debug 2.
;; For a complete description of all compiler switches given the
;; current optimization settings evaluate (explain-compiler-settings).
;;---
;; Current reader case mode: :case-sensitive-lower
cl-user(1): (setq foo #\亜)
#\亜
cl-user(2): foo
#\亜
cl-user(3): (char-code foo)
20124
cl-user(4): (code-char 20124)
#\亜