Ruby1.8で学ぶ、簡単?!記号プログラミング

2010-12-04

全国のプログラマーの皆さま、こんにちは!id:TAKESAKOです。

記号プログラミングってなかなか面白そうだけど、自分にはなんか難しすぎて到底理解できない代物だ…なんて思っていないでしょうか? →それはたぶん大きな勘違いです。

今日は、初心者にわかりやすいと大変評判なプログラミング言語Rubyバージョン1.8を使って、簡単お手軽記号プログラミングを体験してみましょう。

1. 記号だけで顔文字出力

Ruby1.8で以下のプログラムを実行すると標準出力に顔文字「(>_<)/」が表示されます。

$><<"(>_<)/";

これだと1文字ずつ順に追っていけば全部読めるので簡単そうですよね。

2. $>の解説

このプログラムの動作を解説すると、$>は標準出力STDOUTのエイリアスを意味し

STDOUT << "(>_<)/";

と同等の動作となります。

STDOUTの<<メソッドを呼び出すと右側の引数を文字列として渡して標準出力に書き込みます。

これはC++言語でいうところのstd::coutの使い方に相当します。

#include <iostream>
void main()
{
    std::cout << "(>_<)/";
}

このようにRubyは記号の文字だけを用いて、任意の記号を簡単に出力することができるのです。

3. 記号だけで任意の数値を作る方法

次に、プログラマーが一番最初に作る「Hello, world!」を出力するプログラムを記号だけで書いてみようと思うのですが、ここで問題になるのが「H」や「e」の文字をどうやって記号だけで生成するかです。

実はRuby1.8で「?a」と書くと'a'のASCIIコード97の数値が返ります。?の右側には印字可能な文字であれば何でも書けて、「?b」は98、「?c」は99の数値リテラルを意味します。

ということは、?の右側に記号も書くことができて「?!」と書くと'!'のASCIIコード33が返るのです。これらのASCIIコードは普通に数値リテラルとして解釈されるため「?!+?!」のように足し算を行うことができ66の数値が生成できます。

これを応用して足し算や引き算を組み合わせると、すべての数値を記号だけで表現することができます。

NUL: (?!-?!) == 0
SOH: (?"-?!) == 1
STX: (?#-?!) == 2
ETX: (?$-?!) == 3
EOT: (?%-?!) == 4
ENQ: (?&-?!) == 5
ACK: (?'-?!) == 6
\a : (?(-?!) == 7
\b : (?)-?!) == 8
\t : (?*-?!) == 9
\n : (?+-?!) == 10
\v : (?,-?!) == 11
\f : (?--?!) == 12
\r : (?.-?!) == 13
SO: (?/-?!) == 14
SI : (?:-?+) == 15
DLE: (?:-?*) == 16
DC1: (?:-?)) == 17
DC2: (?:-?() == 18
DC3: (?:-?') == 19
DC4: (?:-?&) == 20
NAK: (?:-?%) == 21
SYN: (?:-?$) == 22
ETB: (?:-?#) == 23
CAN: (?:-?") == 24
EM : (?:-?!) == 25
SUB: (?;-?!) == 26
ESC: (?<-?!) == 27
FS : (?=-?!) == 28
GS : (?>-?!) == 29
RS : (??-?!) == 30
US : (?@-?!) == 31
   : (?[-?;) == 32
 ! : (?!) == 33
 " : (?") == 34
 # : (?#) == 35
 $ : (?$) == 36
 % : (?%) == 37
 & : (?&) == 38
 ' : (?') == 39
 ( : (?() == 40
 ) : (?)) == 41
 * : (?*) == 42
 + : (?+) == 43
 , : (?,) == 44
 - : (?-) == 45
 . : (?.) == 46
 / : (?/) == 47
 0 : (?[-?+) == 48
 1 : (?[-?*) == 49
 2 : (?[-?)) == 50
 3 : (?[-?() == 51
 4 : (?[-?') == 52
 5 : (?[-?&) == 53
 6 : (?[-?%) == 54
 7 : (?[-?$) == 55
 8 : (?[-?#) == 56
 9 : (?[-?") == 57
 : : (?:) == 58
 ; : (?;) == 59
 < : (?<) == 60
 = : (?=) == 61
 > : (?>) == 62
 ? : (??) == 63
 @ : (?@) == 64
 A : (?{-?:) == 65
 B : (?!+?!) == 66
 C : (?!+?") == 67
 D : (?!+?#) == 68
 E : (?!+?$) == 69
 F : (?!+?%) == 70
 G : (?!+?&) == 71
 H : (?!+?') == 72
 I : (?!+?() == 73
 J : (?!+?)) == 74
 K : (?!+?*) == 75
 L : (?!+?+) == 76
 M : (?!+?,) == 77
 N : (?!+?-) == 78
 O : (?!+?.) == 79
 P : (?!+?/) == 80
 Q : (?"+?/) == 81
 R : (?#+?/) == 82
 S : (?$+?/) == 83
 T : (?%+?/) == 84
 U : (?&+?/) == 85
 V : (?'+?/) == 86
 W : (?(+?/) == 87
 X : (?)+?/) == 88
 Y : (?*+?/) == 89
 Z : (?++?/) == 90
 [ : (?[) == 91
 \ : (?\) == 92
 ] : (?]) == 93
 ^ : (?^) == 94
 _ : (?_) == 95
 ` : (?`) == 96
 a : (?!+?@) == 97
 b : (?"+?@) == 98
 c : (?#+?@) == 99
 d : (?$+?@) == 100
 e : (?%+?@) == 101
 f : (?&+?@) == 102
 g : (?'+?@) == 103
 h : (?(+?@) == 104
 i : (?)+?@) == 105
 j : (?*+?@) == 106
 k : (?++?@) == 107
 l : (?,+?@) == 108
 m : (?-+?@) == 109
 n : (?.+?@) == 110
 o : (?/+?@) == 111
 p : (?!+?!+?.) == 112
 q : (?!+?!+?/) == 113
 r : (?!+?"+?/) == 114
 s : (?!+?#+?/) == 115
 t : (?:+?:) == 116
 u : (?:+?;) == 117
 v : (?:+?<) == 118
 w : (?:+?=) == 119
 x : (?:+?>) == 120
 y : (?:+??) == 121
 z : (?:+?@) == 122
 { : (?{) == 123
 | : (?|) == 124
 } : (?}) == 125
 ~ : (?~) == 126
DEL: (?!+?^) == 127

Ruby1.8、すごいですね。まさに記号プログラミングのために生まれてきたような言語です。

4. 数値から文字列を作る

数値だけ生成できても「Hello」の文字列に変換できないと嬉しくないので、数値から文字列を生成する方法を次に考えます。

Rubyには文字列に対して<<メソッドを呼ぶと右側の引数が数値の場合、その数値に相当するASCIIコードの文字を追加して返すという面白い仕様があります。
数値に対して<<メソッドを呼ぶとシフト演算になりますが、文字列の場合は以下のような動きになります。

""<<88
# => ""+sprintf("%c",88)
# => ""+"X"
# => "X"

これはメソッドチェーンのように複数回<<メソッドを適用することが可能なので、

""<<88<<89<<90
# => "X"<<89<<90
# => "XY"<<90
# => "XYZ"

のように書けます。

5. 記号だけで任意の文字列を出力する

ということは「Hello, world!\n」の文字列をASCIIコードに分解すると

""<<72<<101<<108<<108<<111<<44<<32<<119<<111<<114<<108<<100<<33<<10
# => "Hello, world!\n"

のように書けます。

これらの数値はすべて記号から生成できることがわかっていますので

""<<(?!+?')<<(?%+?@)<<(?,+?@)<<(?,+?@)<<(?/+?@)<<(?,)<<(?[-?;)<<(?:+?=)<<(?/+?@)<<(?!+?"+?/)<<(?,+?@)<<(?$+?@)<<(?!)<<(?+-?!)
# => "Hello, world!\n"

と書きなおせば記号だけで文字列を構成することができます。

6. 記号だけで「Hello, world!\n」完成

メソッド呼び出しの適用順序を気を付けていれば無駄な括弧を省略することができますので「$><<"Hello, world!\n"」を記号のみで書きなおすと、以下のプログラムになります。

$><<(''<<?!+?'<<?%+?@<<?,+?@<<?,+?@<<?/+?@<<?,<<?[-?;<<?:+?=<<?/+?@<<?!+?"+?/<<?,+?@<<?$+?@<<?!<<?+-?!)

ね、簡単でしょ?

8. 次回予告

しかし「?a」でASCIIコード97の数値が返ってくるという素晴らしい機能はRuby1.9以降からは廃止されて長さ1の文字列"a"を返すように仕様変更されてしまいました。
なので、ここで示したコードは最新のRubyでは動作しません。せっかく頑張ったのに悲しいですね。

次回はRuby1.9以降のバージョンで任意の文字列を出力する方法について解説してみようと思います。