プロローグ
ランバラルはアムロに戦場の厳しさを教えた。では文字列は我々にオブジェクトとは何かを教えてくれるだろうか。君はオブジェクトの涙を見る。
Smalltalkでの文字列
Smalltalkでは文字列もオブジェクトです。i番目の文字を取り出したり、あるいは書き換えたりすることができます。これは大概の言語でもそうだと思います。
残念なお知らせがあります。Smalltalkの文字列には文字を「追加」することができません。追加できないというのは、連結できないという意味ではありません。'abc'という文字列オブジェクトがあった時、'xyz'と連結すると'abcxyz'という新しい文字列オブジェクトが作られます。しかしOrderedCollection(JavaでいうArrayListみたいなもの)に要素を追加するのと同じように、文字列にもその文字列オブジェクト自体に要素を追加できるようにしたい。
テスト
つまり、このようなコードを書けるようにしたい。今回は処理系は Pharo 3.0 を使います。
| hello world |
hello := 'Hello'.
world := 'World'.
hello add: Character space.
hello addAll: world.
hello "==> 'Hello World'"
Pharo 3.0の標準のライブラリでは以下のようになります。
叱られちゃった。てへ。
でもね、Smalltalkではエラーメッセージは異常終了ではないのですよ。まさにこれからプログラミングが始まりますよという、オープニングファンファーレなのです。(キリリ
Stringクラスを書き換える
文字列はオブジェクトです。オブジェクトというのは、ユーザー(プログラマ)が中の構造やコードを見ることができ、また、自由に書き換えることができるものを指します。さあ、文字列オブジェクトに「要素の追加の仕方」を教えてあげましょう。
Symbolを守ってあげる
SmalltalkではSymbol(書き換えができない文字列)は文字列の一種になっています。文字列が要素の追加ができるようになると、Symbolも要素を追加できてしまうので、Symbolには影響が及ばないように守ってあげましょう。
add: aCharacter
^ self shouldNotImplement
Stringにadd:を教える
さあ、安心して文字列に要素の追加の仕方を教えましょう。とても簡単です。
add: aCharacter
| expanded |
expanded := self copyWith: aCharacter.
self become: expanded.
^ aCharacter
copyWith:
というのは、引数aCharacter
で与えた文字を末尾に追加した「新しい文字列」を生成するためのメッセージです。次に、self become: expanded
で、自分自身がその「新しい文字列」に成り代ります。内容をコピーするのではなく、オブジェクトとしてのアイデンティティを交換します。すると、新しい「自分自身」は、aCharacter
を追加された状態になっています。
テスト
さあ、試してみましょう。
バッチリですね。変数hello
を書き換えたのではなく、'Hello'
という文字列自体がどんどん伸張していきます。
ええい、要素の削除はできんのか!
はい、同じやり方でできます。例えばこんな感じ。
remove: aCharacter ifAbsent: errorBlock
^ self shouldNotImplement
remove: aCharacter ifAbsent: errorBlock
| index shrinked |
index := self indexOf: aCharacter ifAbsent: [ ^ errorBlock value ].
shrinked := (self copyFrom: 1 to: index - 1) , (self copyFrom: index + 1 to: self size).
self become: shrinked.
^ aCharacter
落とし穴に落ちるとは、こういうことだー!(ぼかーん
調子に乗ってリテラル文字列を書き換えるとひどい目に合います。例えばコンパイラがリテラルの生成をケチると、こういう目にあいます。
最後、'Hello'
という文字列リテラルを返しているので、結果は'Hello'
になるはずです。しかし何故か'Hello World'
が。これは2箇所の同じ字面の文字列リテラルを1つのオブジェクトで使いまわしているからですね。要素の追加に限らず リテラル文字列は書き換える前にコピーして使いましょう。
おわりに
標準ライブラリであろうが、文字列のような基本的なオブジェクトであろうが、欲しいと思った機能は実現する。実現できる。それがオブジェクト指向プログラミングの楽しさだと思います。鳴かぬなら鳴き方を教えてやろうホトトギス。